Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 46 additions & 2 deletions code/go/0chain.net/blobbercore/handler/file_command_add.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@ package handler
import (
"context"
"encoding/json"
"errors"
"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/datastore"
"github.com/0chain/blobber/code/go/0chain.net/blobbercore/filestore"
"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"
"github.com/0chain/gosdk/constants"
"github.com/0chain/gosdk/zboxcore/fileref"
"go.uber.org/zap"
"gorm.io/gorm"
)

// AddFileCommand command for resuming file
Expand Down Expand Up @@ -42,8 +46,10 @@ func (cmd *AddFileCommand) IsAuthorized(ctx context.Context, req *http.Request,
return common.NewError("duplicate_file", "File at path already exists")
}

//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{}
err = cmd.validatePath(ctx, allocationObj, fileChanger.Path)
if err != nil {
return err
}

if fileChanger.ChunkSize <= 0 {
fileChanger.ChunkSize = fileref.CHUNK_SIZE
Expand All @@ -54,6 +60,44 @@ func (cmd *AddFileCommand) IsAuthorized(ctx context.Context, req *http.Request,
return nil
}

func (cmd *AddFileCommand) validatePath(ctx context.Context, allocationObj *allocation.Allocation, filePath string) error {

walker := reference.NewRefWalkerFromPath(filePath)

// only 1 directory level deep: ["", "file"]
if walker.Length() <= 2 {

}

walker.Last()
db := datastore.GetStore().GetTransaction(ctx)

for walker.Back() {
currentPath, _ := walker.Path()

if currentPath == "/" {
return nil
}

lookupHash := reference.GetReferenceLookup(allocationObj.ID, currentPath)

var fileType string
err := db.Raw(`SELECT type FROM "reference_objects" WHERE lookup_hash = ?`, lookupHash).Pluck("type", &fileType).Error
if err != nil {
if errors.Is(gorm.ErrRecordNotFound, err) {
return nil
}
return err
}

if fileType == fileref.FILE {
return common.NewError("invalid_path", fmt.Sprintf("parent path %v is of file type", currentPath))
}
}

return nil
}

// ProcessContent flush file to FileStorage
func (cmd *AddFileCommand) ProcessContent(ctx context.Context, req *http.Request, allocationObj *allocation.Allocation, connectionObj *allocation.AllocationChangeCollector) (blobberhttp.UploadResult, error) {
result := blobberhttp.UploadResult{}
Expand Down
155 changes: 155 additions & 0 deletions code/go/0chain.net/blobbercore/reference/ref_walker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package reference

import (
"path"
"strings"
)

func NewRefWalkerFromPath(p string) *RefWalker {
return NewRefWalker(strings.Split(path.Clean(p), "/"))
}

// NewRefWalker wrap dirs as RefWaller
func NewRefWalker(dirs []string) *RefWalker {

return &RefWalker{
items: dirs,
length: len(dirs),
index: 0,
}
}

type RefWalker struct {
items []string
length int
index int
}

// Name get current dir name
func (d *RefWalker) Name() (string, bool) {
if d == nil {
return "", false
}

// current is root
if d.index == 0 {
return "/", true
}

if d.index < d.length {
return d.items[d.index], true
}

return "", false
}

// Path get current dir path
func (d *RefWalker) Path() (string, bool) {
if d == nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should return error when d is nil, or when index is >= length, otherwise users will have to check the code to know what happened when the returned value is "".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

return "", false
}

// current is root
if d.index == 0 {
return "/", true
}

if d.index < d.length {
return "/" + path.Join(d.items[:d.index+1]...), true
}

return "", false
}

// Parent get curerent parent path
func (d *RefWalker) Parent() (string, bool) {
if d == nil {
return "", false
}

// current is root
if d.index == 0 {
return "", true
}

return "/" + path.Join(d.items[:d.index]...), true
}

// Level get current dir level
func (d *RefWalker) Level() int {
if d == nil {
return 0
}

return d.index + 1
}

// Level get the numbers of dir
func (d *RefWalker) Length() int {
if d == nil {
return 0
}

return len(d.items)
}

// Top move to root dir
func (d *RefWalker) Top() bool {
if d == nil {
return false
}

d.index = 0

return true
}

// Last move to last sub dir
func (d *RefWalker) Last() bool {
if d == nil {
return false
}

if d.length > 0 {
d.index = d.length - 1
return true
}

return false
}

// Back back to parent dir
func (d *RefWalker) Back() bool {
if d == nil {
return false
}

i := d.index - 1

// it is root
if i < 0 {
return false
}

d.index = i

return true
}

// Forward go to sub dir
func (d *RefWalker) Forward() bool {
if d == nil {
return false
}

i := d.index + 1

// it is root
if i >= len(d.items) {
return false
}

d.index = i

return true
}
96 changes: 96 additions & 0 deletions code/go/0chain.net/blobbercore/reference/ref_walker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package reference

import (
"path"
"strings"
"testing"

"github.com/stretchr/testify/require"
)

func TestRefWalker(t *testing.T) {
list := []struct {
TestName string
TestPath string
WalkFunc func(w *RefWalker) bool
WalkResult bool
Name string
Level int
Path string
Parent string
}{
{TestName: "invalid_path", TestPath: "", WalkFunc: func(w *RefWalker) bool {
return w.Top()
}, WalkResult: true, Name: "/", Level: 1, Path: "/", Parent: ""},

{TestName: "top", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
return w.Top()
}, WalkResult: true, Name: "/", Level: 1, Path: "/", Parent: ""},

{TestName: "forward_2", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
w.Top()
return w.Forward()
}, WalkResult: true, Name: "d1", Level: 2, Path: "/d1", Parent: "/"},

{TestName: "forward_3", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
w.Top()
w.Forward()
return w.Forward()
}, WalkResult: true, Name: "d2", Level: 3, Path: "/d1/d2", Parent: "/d1"},

{TestName: "forward_last", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
w.Last()
return w.Forward()
}, WalkResult: false, Name: "d3", Level: 4, Path: "/d1/d2/d3", Parent: "/d1/d2"},

{TestName: "last", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
return w.Last()
}, WalkResult: true, Name: "d3", Level: 4, Path: "/d1/d2/d3", Parent: "/d1/d2"},

{TestName: "back_2", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
w.Last()
return w.Back()
}, WalkResult: true, Name: "d2", Level: 3, Path: "/d1/d2", Parent: "/d1"},

{TestName: "back_3", TestPath: "/d1/d2/d3", WalkFunc: func(w *RefWalker) bool {
w.Last()
w.Back()
return w.Back()
}, WalkResult: true, Name: "d1", Level: 2, Path: "/d1", Parent: "/"},

{TestName: "back_3_with_invalid_dir", TestPath: `/d1///d2/d3`, WalkFunc: func(w *RefWalker) bool {
w.Last()
w.Back()
return w.Back()
}, WalkResult: true, Name: "d1", Level: 2, Path: "/d1", Parent: "/"},
}

for _, it := range list {
t.Run(it.TestName, func(test *testing.T) {

dirs := strings.Split(path.Clean(it.TestPath), "/")

rw := NewRefWalker(dirs)
result := it.WalkFunc(rw)

r := require.New(test)

r.Equal(it.WalkResult, result)
name, ok := rw.Name()
r.True(ok)
r.Equal(it.Name, name)

r.Equal(it.Level, rw.Level())

path, ok := rw.Path()
r.True(ok)
r.Equal(it.Path, path)

parentPath, ok := rw.Parent()
r.True(ok)
r.Equal(it.Parent, parentPath)

})
}

}
17 changes: 17 additions & 0 deletions code/go/0chain.net/blobbercore/util/strings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package util

import (
"strings"
)

// SplitPath split files from a path
func SplitPath(path string) []string {
items := make([]string, 0)
for _, it := range strings.Split(path, "/") {
if len(it) > 0 {
items = append(items, it)
}
}

return items
}
47 changes: 47 additions & 0 deletions code/go/0chain.net/blobbercore/util/strings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package util

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestSplitPath(t *testing.T) {

tests := []struct {
name string
path string
items []string
}{
{
name: "empty root",
path: "/",
items: []string{},
},
{
name: "only 1 directory level deep",
path: "/file1",
items: []string{"file1"},
},
{
name: "nested directories",
path: "/dir1/dir2/dir3/file1",
items: []string{"dir1", "dir2", "dir3", "file1"},
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding cases for empty subpath perhaps, e.g. /dir1/dir2//dir3///dir4

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

}

for _, it := range tests {
t.Run(it.name, func(t *testing.T) {

files := SplitPath(it.path)

require.Len(t, files, len(it.items))

for i, v := range it.items {
require.Equal(t, v, files[i])
}

})
}

}