diff --git a/code/go/0chain.net/blobbercore/handler/file_command.go b/code/go/0chain.net/blobbercore/handler/file_command.go index 08ed1e076..3ea8bae7b 100644 --- a/code/go/0chain.net/blobbercore/handler/file_command.go +++ b/code/go/0chain.net/blobbercore/handler/file_command.go @@ -2,10 +2,14 @@ package handler import ( "context" + "fmt" "net/http" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/blobberhttp" + "github.com/0chain/blobber/code/go/0chain.net/blobbercore/reference" + "github.com/0chain/blobber/code/go/0chain.net/core/common" + "github.com/0chain/blobber/code/go/0chain.net/core/logging" ) // FileCommand execute command for a file operation @@ -37,3 +41,27 @@ func createFileCommand(req *http.Request) FileCommand { return &AddFileCommand{} } } + +// validateParentPathType validates against any parent path not being directory. +func validateParentPathType(ctx context.Context, allocationID, fPath string) error { + paths, err := common.GetParentPaths(fPath) + if err != nil { + return err + } + + refs, err := reference.GetRefsTypeFromPaths(ctx, allocationID, paths) + if err != nil { + logging.Logger.Error(err.Error()) + return common.NewError("database_error", "Got error while getting parent refs") + } + + for _, ref := range refs { + if ref == nil { + continue + } + if ref.Type == reference.FILE { + return common.NewError("invalid_path", fmt.Sprintf("parent path %v is of file type", ref.Path)) + } + } + return nil +} diff --git a/code/go/0chain.net/blobbercore/handler/file_command_add.go b/code/go/0chain.net/blobbercore/handler/file_command_add.go index 94c69a251..85933f64e 100644 --- a/code/go/0chain.net/blobbercore/handler/file_command_add.go +++ b/code/go/0chain.net/blobbercore/handler/file_command_add.go @@ -3,7 +3,9 @@ package handler import ( "context" "encoding/json" + "fmt" "net/http" + "path/filepath" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/allocation" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/blobberhttp" @@ -36,12 +38,25 @@ func (cmd *AddFileCommand) IsAuthorized(ctx context.Context, req *http.Request, return common.NewError("invalid_parameters", "Invalid parameters. Error parsing the meta data for upload."+err.Error()) } - exisitingFileRef, _ := reference.GetReference(ctx, allocationObj.ID, fileChanger.Path) - if exisitingFileRef != nil { + if !filepath.IsAbs(fileChanger.Path) { + return common.NewError("invalid_path", fmt.Sprintf("%v is not absolute path", fileChanger.Path)) + } + + isExist, err := reference.IsRefExist(ctx, allocationObj.ID, fileChanger.Path) + if err != nil { + logging.Logger.Error(err.Error()) + return common.NewError("database_error", "Got db error while getting ref") + } + + if isExist { return common.NewError("duplicate_file", "File at path already exists") } + if err := validateParentPathType(ctx, allocationObj.ID, fileChanger.Path); err != nil { + return err + } + //create a FixedMerkleTree instance first, it will be reloaded from db in cmd.reloadChange if it is not first chunk //cmd.fileChanger.FixedMerkleTree = &util.FixedMerkleTree{} diff --git a/code/go/0chain.net/blobbercore/handler/handler_test.go b/code/go/0chain.net/blobbercore/handler/handler_test.go index f09bd17d8..dd092f71e 100644 --- a/code/go/0chain.net/blobbercore/handler/handler_test.go +++ b/code/go/0chain.net/blobbercore/handler/handler_test.go @@ -3,7 +3,6 @@ package handler import ( "bytes" "encoding/json" - "errors" "fmt" "io" "mime/multipart" @@ -864,7 +863,7 @@ func TestHandlers_Requiring_Signature(t *testing.T) { q.Set("path", path) q.Set("new_name", newName) q.Set("connection_id", connectionID) - q.Set("dest", "dest") + q.Set("dest", "/dest") url.RawQuery = q.Encode() r, err := http.NewRequest(http.MethodPost, url.String(), nil) @@ -915,13 +914,15 @@ func TestHandlers_Requiring_Signature(t *testing.T) { mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). WithArgs(alloc.ID, lookUpHash). WillReturnRows( - sqlmock.NewRows([]string{"type"}). - AddRow(reference.FILE), + sqlmock.NewRows([]string{"type", "name"}). + AddRow(reference.FILE, "path"), ) - mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects" WHERE`)). - WithArgs(aa, aa). - WillReturnError(errors.New("")) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT "path","type" FROM "reference_objects" WHERE`)). + WillReturnRows( + sqlmock.NewRows([]string{"path", "type"}). + AddRow("/dest", reference.DIRECTORY), + ) mock.ExpectExec(`INSERT INTO "allocation_connections"`). WithArgs(aa, aa, aa, aa, aa, aa, aa). @@ -1034,7 +1035,9 @@ func TestHandlers_Requiring_Signature(t *testing.T) { } q := url.Query() - formFieldByt, err := json.Marshal(&allocation.UpdateFileChanger{}) + formFieldByt, err := json.Marshal( + &allocation.UpdateFileChanger{ + BaseFileChanger: allocation.BaseFileChanger{Path: path}}) if err != nil { t.Fatal(err) } @@ -1110,9 +1113,16 @@ func TestHandlers_Requiring_Signature(t *testing.T) { ) mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "reference_objects"`)). - WithArgs(aa). + WithArgs(aa, aa). WillReturnError(gorm.ErrRecordNotFound) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "reference_objects"`)). + WithArgs(aa, aa). + WillReturnRows( + sqlmock.NewRows([]string{"count"}). + AddRow(0), + ) + mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "allocation_connections" WHERE`)). WithArgs(connectionID, alloc.ID, alloc.OwnerID, allocation.DeletedConnection). WillReturnRows( diff --git a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go index bddb45e4a..85c0c6fcc 100644 --- a/code/go/0chain.net/blobbercore/handler/object_operation_handler.go +++ b/code/go/0chain.net/blobbercore/handler/object_operation_handler.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "errors" + "fmt" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/blobberhttp" "github.com/0chain/blobber/code/go/0chain.net/blobbercore/stats" @@ -790,10 +791,30 @@ func (fsh *StorageHandler) CopyObject(ctx context.Context, r *http.Request) (int if err != nil { return nil, common.NewError("invalid_parameters", "Invalid file path. "+err.Error()) } + newPath := filepath.Join(destPath, objectRef.Name) - destRef, _ := reference.GetReference(ctx, allocationID, newPath) - if destRef != nil { - return nil, common.NewError("invalid_parameters", "Invalid destination path. Object Already exists.") + paths, err := common.GetParentPaths(newPath) + if err != nil { + return nil, err + } + + paths = append(paths, newPath) + + refs, err := reference.GetRefsTypeFromPaths(ctx, allocationID, paths) + if err != nil { + Logger.Error("Database error", zap.Error(err)) + return nil, common.NewError("database_error", fmt.Sprintf("Got db error while getting refs for %v", paths)) + } + + for _, ref := range refs { + switch ref.Path { + case newPath: + return nil, common.NewError("invalid_parameters", "Invalid destination path. Object Already exists.") + default: + if ref.Type == reference.FILE { + return nil, common.NewError("invalid_path", fmt.Sprintf("%v is of file type", ref.Path)) + } + } } allocationChange := &allocation.AllocationChange{} @@ -880,6 +901,10 @@ func (fsh *StorageHandler) CreateDir(ctx context.Context, r *http.Request) (*blo return nil, common.NewError("invalid_parameters", "Invalid dir path passed") } + if !filepath.IsAbs(dirPath) { + return nil, common.NewError("invalid_path", fmt.Sprintf("%v is not absolute path", dirPath)) + } + exisitingRef := fsh.checkIfFileAlreadyExists(ctx, allocationID, dirPath) if allocationObj.OwnerID != clientID && allocationObj.PayerID != clientID { return nil, common.NewError("invalid_operation", "Operation needs to be performed by the owner or the payer of the allocation") @@ -889,6 +914,10 @@ func (fsh *StorageHandler) CreateDir(ctx context.Context, r *http.Request) (*blo return nil, common.NewError("duplicate_file", "File at path already exists") } + if err := validateParentPathType(ctx, allocationID, dirPath); err != nil { + return nil, err + } + connectionID := r.FormValue("connection_id") if connectionID == "" { return nil, common.NewError("invalid_parameters", "Invalid connection id passed") diff --git a/code/go/0chain.net/blobbercore/handler/upload_integration_test.go b/code/go/0chain.net/blobbercore/handler/upload_integration_test.go index b06d7c048..1e938a373 100644 --- a/code/go/0chain.net/blobbercore/handler/upload_integration_test.go +++ b/code/go/0chain.net/blobbercore/handler/upload_integration_test.go @@ -25,7 +25,8 @@ func TestBlobberGRPCService_UploadFile(t *testing.T) { pubKeyBytes, _ := hex.DecodeString(pubKey) clientId := encryption.Hash(pubKeyBytes) - formFieldByt, err := json.Marshal(&allocation.UpdateFileChanger{BaseFileChanger: allocation.BaseFileChanger{Filename: `helper_integration_test.go`}}) + path := "/some_file" + formFieldByt, err := json.Marshal(&allocation.UpdateFileChanger{BaseFileChanger: allocation.BaseFileChanger{Filename: `helper_integration_test.go`, Path: path}}) if err != nil { t.Fatal(err) } @@ -68,7 +69,7 @@ func TestBlobberGRPCService_UploadFile(t *testing.T) { input: &blobbergrpc.UploadFileRequest{ Allocation: allocationTx, - Path: "/some_file", + Path: path, ConnectionId: "connection_id", Method: "POST", UploadMeta: string(formFieldByt), diff --git a/code/go/0chain.net/blobbercore/reference/ref.go b/code/go/0chain.net/blobbercore/reference/ref.go index 80deec905..015d7de97 100644 --- a/code/go/0chain.net/blobbercore/reference/ref.go +++ b/code/go/0chain.net/blobbercore/reference/ref.go @@ -235,6 +235,55 @@ func GetReferenceFromLookupHash(ctx context.Context, allocationID, path_hash str return nil, err } +// GetRefType Select type from ref and return it +func GetRefType(ctx context.Context, allocationID, path string) (string, error) { + ref := new(Ref) + db := datastore.GetStore().GetTransaction(ctx) + err := db.Select("type").Where("allocation_id=? AND path=?", allocationID, path).First(ref).Error + if err != nil { + return "", err + } + return ref.Type, nil +} + +// GetRefWithID Return Ref with only ID selected in sql query +func GetRefWithID(ctx context.Context, allocationID, path string) (*Ref, error) { + ref := new(Ref) + db := datastore.GetStore().GetTransaction(ctx) + err := db.Select("id").Where("allocation_id=? AND path=?", allocationID, path).First(ref).Error + if err != nil { + return nil, err + } + return ref, nil +} + +// IsRefExist checks if ref with given path exists and returns error other than gorm.ErrRecordNotFound +func IsRefExist(ctx context.Context, allocationID, path string) (bool, error) { + db := datastore.GetStore().GetTransaction(ctx) + var count int64 + if err := db.Model(&Ref{}).Where("allocation_id=? AND path=?", allocationID, path).Count(&count).Error; err != nil { + return false, err + } + + return count > 0, nil +} + +// GetRefsTypeFromPaths Give list of paths it will return refs of respective path with only Type and Path selected in sql query +func GetRefsTypeFromPaths(ctx context.Context, allocationID string, paths []string) (refs []*Ref, err error) { + if len(paths) == 0 { + return + } + + db := datastore.GetStore().GetTransaction(ctx) + db = db.Select("path", "type") + for _, p := range paths { + db = db.Or(Ref{AllocationID: allocationID, Path: p}) + } + + err = db.Find(&refs).Error + return +} + func GetSubDirsFromPath(p string) []string { path := p parent, cur := filepath.Split(path) diff --git a/code/go/0chain.net/core/common/utils.go b/code/go/0chain.net/core/common/utils.go index 558bad211..9b68754de 100644 --- a/code/go/0chain.net/core/common/utils.go +++ b/code/go/0chain.net/core/common/utils.go @@ -1,6 +1,10 @@ package common -import "fmt" +import ( + "fmt" + "path/filepath" + "strings" +) // IsEmpty checks whether the input string is empty or not func IsEmpty(s string) bool { @@ -22,3 +26,23 @@ func ToKey(key interface{}) string { func IsEqual(key1, key2 string) bool { return key1 == key2 } + +// getParentPaths For path /a/b/c.txt, will return [/a,/a/b] +func GetParentPaths(fPath string) ([]string, error) { + if fPath == "" { + return nil, nil + } + + fPath = filepath.Clean(fPath) + if !filepath.IsAbs(fPath) { + return nil, NewError("invalid_path", fmt.Sprintf("%v is not absolute path", fPath)) + } + splittedPaths := strings.Split(fPath, "/") + var paths []string + + for i := 0; i < len(splittedPaths); i++ { + subPath := strings.Join(splittedPaths[0:i], "/") + paths = append(paths, subPath) + } + return paths[2:], nil +}