const minFilePathLen = 2
func (fp *FilePath) UnmarshalBinary(b []byte) error {
+ if b == nil {
+ return nil
+ }
if len(b) < minFilePathLen {
return errors.New("insufficient bytes")
}
}
return fp.String()
}
+
+func readPath(fileRoot string, filePath, fileName []byte) (fullPath string, err error) {
+ var fp FilePath
+ if filePath != nil {
+ if err = fp.UnmarshalBinary(filePath); err != nil {
+ return "", err
+ }
+ }
+
+ fullPath = path.Join(
+ "/",
+ fileRoot,
+ fp.String(),
+ path.Join("/", string(fileName)),
+ )
+
+ return fullPath, nil
+}
)
func TestFilePath_UnmarshalBinary(t *testing.T) {
- type fields struct {
- ItemCount []byte
- Items []FilePathItem
- }
type args struct {
b []byte
}
})
}
}
+
+func Test_readPath(t *testing.T) {
+ type args struct {
+ fileRoot string
+ filePath []byte
+ fileName []byte
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ {
+ name: "when filePath is invalid",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: []byte{
+ 0x61,
+ },
+ fileName: []byte{
+ 0x61, 0x61, 0x61,
+ },
+ },
+ want: "",
+ wantErr: true,
+ },
+ {
+ name: "when filePath is nil",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: nil,
+ fileName: []byte("foo"),
+
+ },
+ want: "/usr/local/var/mobius/Files/foo",
+ },
+ {
+ name: "when fileName contains .. ",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: nil,
+ fileName: []byte("../../../foo"),
+ },
+ want: "/usr/local/var/mobius/Files/foo",
+ },
+ {
+ name: "when filePath contains .. ",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: []byte{
+ 0x00, 0x02,
+ 0x00, 0x00,
+ 0x03,
+ 0x2e, 0x2e, 0x2f,
+ 0x00, 0x00,
+ 0x08,
+ 0x41, 0x20, 0x53, 0x75, 0x62, 0x44, 0x69, 0x72,
+ },
+ fileName: []byte("foo"),
+ },
+ want: "/usr/local/var/mobius/Files/A SubDir/foo",
+ },
+ {
+ name: "when a filePath entry contains .. ",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: []byte{
+ 0x00, 0x01,
+ 0x00, 0x00,
+ 0x0b,
+ 0x2e, 0x2e, 0x2f, 0x41, 0x20, 0x53, 0x75, 0x62, 0x44, 0x69, 0x72,
+ },
+ fileName: []byte("foo"),
+ },
+ want: "/usr/local/var/mobius/Files/A SubDir/foo",
+ },
+ {
+ name: "when filePath and fileName are nil",
+ args: args{
+ fileRoot: "/usr/local/var/mobius/Files",
+ filePath: nil,
+ fileName: nil,
+ },
+ want: "/usr/local/var/mobius/Files",
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := readPath(tt.args.fileRoot, tt.args.filePath, tt.args.fileName)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("readPath() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("readPath() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
\ No newline at end of file
import (
"encoding/binary"
- "fmt"
"os"
)
return out
}
-func NewFlattenedFileObject(filePath, fileName string) (*flattenedFileObject, error) {
- file, err := os.Open(fmt.Sprintf("%v/%v", filePath, fileName))
+func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte) (*flattenedFileObject, error) {
+ fullFilePath, err := readPath(fileRoot, filePath, fileName)
+ if err != nil {
+ return nil, err
+ }
+ file, err := os.Open(fullFilePath)
if err != nil {
return nil, err
}
return &flattenedFileObject{
FlatFileHeader: NewFlatFileHeader(),
- FlatFileInformationFork: NewFlatFileInformationFork(fileName),
+ FlatFileInformationFork: NewFlatFileInformationFork(string(fileName)),
FlatFileDataForkHeader: FlatFileDataForkHeader{
ForkType: []byte("DATA"),
CompressionType: []byte{0, 0, 0, 0},
import (
"bytes"
"encoding/hex"
- "github.com/davecgh/go-spew/spew"
- "reflect"
+ "fmt"
+ "github.com/stretchr/testify/assert"
+ "os"
"testing"
)
}
}
-//
-//func TestNewFlattenedFileObject(t *testing.T) {
-// ffo := NewFlattenedFileObject("test/config/files", "testfile.txt")
-//
-// dataSize := ffo.FlatFileDataForkHeader.DataSize
-// want := []byte{0, 0, 0, 0x17}
-// if bytes.Compare(dataSize, want) != 0 {
-// t.Errorf("%q, want %q", dataSize, want)
-// }
-//
-// comment := ffo.FlatFileInformationFork.Comment
-// want = []byte("Test Comment")
-// if bytes.Compare(ffo.FlatFileInformationFork.Comment, want) != 0 {
-// t.Errorf("%q, want %q", comment, want)
-// }
-//}
-
func TestNewFlattenedFileObject(t *testing.T) {
type args struct {
- filePath string
- fileName string
+ fileRoot string
+ filePath []byte
+ fileName []byte
}
tests := []struct {
name string
args args
want *flattenedFileObject
- wantErr bool
+ wantErr assert.ErrorAssertionFunc
}{
{
- name: "when file path is valid",
+ name: "with valid file",
args: args{
- filePath: "./test/config/Files/",
- fileName: "testfile.txt",
+ fileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
+ fileName: []byte("testfile.txt"),
+ filePath: []byte{0, 0},
},
want: &flattenedFileObject{
FlatFileHeader: NewFlatFileHeader(),
FlatFileInformationForkHeader: FlatFileInformationForkHeader{},
FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt"),
- FlatFileDataForkHeader: FlatFileDataForkHeader{
+ FlatFileDataForkHeader: FlatFileDataForkHeader{
ForkType: []byte("DATA"),
CompressionType: []byte{0, 0, 0, 0},
RSVD: []byte{0, 0, 0, 0},
DataSize: []byte{0x00, 0x00, 0x00, 0x17},
},
- FileData: nil,
+ FileData: nil,
},
- wantErr: false,
+ wantErr: assert.NoError,
},
{
name: "when file path is invalid",
args: args{
- filePath: "./nope/",
- fileName: "also-nope.txt",
+ fileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
+ fileName: []byte("nope.txt"),
},
- want: nil,
- wantErr: true,
+ want: nil,
+ wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- got, err := NewFlattenedFileObject(tt.args.filePath, tt.args.fileName)
- spew.Dump(got)
- if (err != nil) != tt.wantErr {
- t.Errorf("NewFlattenedFileObject() error = %v, wantErr %v", err, tt.wantErr)
+ got, err := NewFlattenedFileObject(tt.args.fileRoot, tt.args.filePath, tt.args.fileName)
+ if !tt.wantErr(t, err, fmt.Sprintf("NewFlattenedFileObject(%v, %v, %v)", tt.args.fileRoot, tt.args.filePath, tt.args.fileName)) {
return
}
- if !reflect.DeepEqual(got, tt.want) {
- t.Errorf("NewFlattenedFileObject() got = %v, want %v", got, tt.want)
- }
+ assert.Equalf(t, tt.want, got, "NewFlattenedFileObject(%v, %v, %v)", tt.args.fileRoot, tt.args.filePath, tt.args.fileName)
})
}
-}
\ No newline at end of file
+}
switch fileTransfer.Type {
case FileDownload:
- fullFilePath := fmt.Sprintf("%v/%v", s.Config.FileRoot+string(fileTransfer.FilePath), string(fileTransfer.FileName))
+ fullFilePath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
+ if err != nil {
+ return err
+ }
ffo, err := NewFlattenedFileObject(
- s.Config.FileRoot+string(fileTransfer.FilePath),
- string(fileTransfer.FileName),
+ s.Config.FileRoot,
+ fileTransfer.FilePath,
+ fileTransfer.FileName,
)
if err != nil {
return err
return err
}
- file, err := os.Open(fullFilePath)
+ file, err := FS.Open(fullFilePath)
if err != nil {
return err
}
//
// This notifies the server to send the next item header
- var fh FilePath
- _ = fh.UnmarshalBinary(fileTransfer.FilePath)
- fullFilePath := fmt.Sprintf("%v/%v", s.Config.FileRoot+fh.String(), string(fileTransfer.FileName))
+ fullFilePath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
+ if err != nil {
+ return err
+ }
basePathLen := len(fullFilePath)
splitPath := strings.Split(path, "/")
- ffo, err := NewFlattenedFileObject(strings.Join(splitPath[:len(splitPath)-1], "/"), info.Name())
+ ffo, err := NewFlattenedFileObject(
+ strings.Join(splitPath[:len(splitPath)-1], "/"),
+ nil,
+ []byte(info.Name()),
+ )
if err != nil {
return err
}
})
case FolderUpload:
- dstPath := s.Config.FileRoot + ReadFilePath(fileTransfer.FilePath) + "/" + string(fileTransfer.FileName)
+ dstPath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
+ if err != nil {
+ return err
+ }
s.Logger.Infow(
"Folder upload started",
"transactionRef", fileTransfer.ReferenceNumber,
"RemoteAddr", conn.RemoteAddr().String(),
"dstPath", dstPath,
- "TransferSize", fileTransfer.TransferSize,
+ "TransferSize", fmt.Sprintf("%x", fileTransfer.TransferSize),
"FolderItemCount", fileTransfer.FolderItemCount,
)
// Check if the target folder exists. If not, create it.
- if _, err := os.Stat(dstPath); os.IsNotExist(err) {
- s.Logger.Infow("Target path does not exist; Creating...", "dstPath", dstPath)
- if err := os.Mkdir(dstPath, 0777); err != nil {
+ if _, err := FS.Stat(dstPath); os.IsNotExist(err) {
+ s.Logger.Infow("Creating target path", "dstPath", dstPath)
+ if err := FS.Mkdir(dstPath, 0777); err != nil {
s.Logger.Error(err)
}
}
func StartTestServer() (*Server, context.Context, context.CancelFunc) {
ctx, cancelRoot := context.WithCancel(context.Background())
+ FS = OSFileStore{}
+
srv, err := NewServer("test/config/", "localhost", 0, NewTestLogger())
if err != nil {
panic(err)
}
func TestHandshake(t *testing.T) {
+ mfs := MockFileStore{}
+ fh, _ := os.Open("./test/config/Agreement.txt")
+ mfs.On("Open", "/test/config/Agreement.txt").Return(fh, nil)
+ fh, _ = os.Open("./test/config/config.yaml")
+ mfs.On("Open", "/test/config/config.yaml").Return(fh, nil)
+ FS = mfs
+ spew.Dump(mfs)
+
srv, _, cancelFunc := StartTestServer()
defer cancelFunc()
func tranAssertEqual(t *testing.T, tran1, tran2 []Transaction) bool {
var newT1 []Transaction
var newT2 []Transaction
- for _, trans := range tran1{
- trans.ID = []byte{0,0,0,0}
+ for _, trans := range tran1 {
+ trans.ID = []byte{0, 0, 0, 0}
newT1 = append(newT1, trans)
}
- for _, trans := range tran2{
- trans.ID = []byte{0,0,0,0}
+ for _, trans := range tran2 {
+ trans.ID = []byte{0, 0, 0, 0}
newT2 = append(newT2, trans)
}
- spew.Dump(newT1, newT2)
-
return assert.Equal(t, newT1, newT2)
-}
\ No newline at end of file
+}
}
func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- fileName := string(t.GetField(fieldFileName).Data)
- filePath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFilePath).Data)
+ fileName := t.GetField(fieldFileName).Data
+ filePath := t.GetField(fieldFilePath).Data
- ffo, err := NewFlattenedFileObject(filePath, fileName)
+ ffo, err := NewFlattenedFileObject(cc.Server.Config.FileRoot, filePath, fileName)
if err != nil {
return res, err
}
res = append(res, cc.NewReply(t,
- NewField(fieldFileName, []byte(fileName)),
+ NewField(fieldFileName, fileName),
NewField(fieldFileTypeString, ffo.FlatFileInformationFork.TypeSignature),
NewField(fieldFileCreatorString, ffo.FlatFileInformationFork.CreatorSignature),
NewField(fieldFileComment, ffo.FlatFileInformationFork.Comment),
// * 210 File comment Optional
// Fields used in the reply: None
func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- fileName := string(t.GetField(fieldFileName).Data)
- filePath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFilePath).Data)
+ fileName := t.GetField(fieldFileName).Data
+ filePath := t.GetField(fieldFilePath).Data
+
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+ if err != nil {
+ return res, err
+ }
+
+ fullNewFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, t.GetField(fieldFileNewName).Data)
+ if err != nil {
+ return nil, err
+ }
+
//fileComment := t.GetField(fieldFileComment).Data
fileNewName := t.GetField(fieldFileNewName).Data
if fileNewName != nil {
- path := filePath + "/" + fileName
- fi, err := os.Stat(path)
+ fi, err := FS.Stat(fullFilePath)
if err != nil {
return res, err
}
}
}
- err = os.Rename(filePath+"/"+fileName, filePath+"/"+string(fileNewName))
+ err = os.Rename(fullFilePath, fullNewFilePath)
if os.IsNotExist(err) {
- res = append(res, cc.NewErrReply(t, "Cannot rename file "+fileName+" because it does not exist or cannot be found."))
+ res = append(res, cc.NewErrReply(t, "Cannot rename file "+string(fileName)+" because it does not exist or cannot be found."))
return res, err
}
}
// * 202 File path
// Fields used in the reply: none
func HandleDeleteFile(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- fileName := string(t.GetField(fieldFileName).Data)
- filePath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFilePath).Data)
+ fileName := t.GetField(fieldFileName).Data
+ filePath := t.GetField(fieldFilePath).Data
- path := filePath + fileName
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+ if err != nil {
+ return res, err
+ }
- cc.Server.Logger.Debugw("Delete file", "src", path)
+ cc.Server.Logger.Debugw("Delete file", "src", fullFilePath)
- fi, err := os.Stat(path)
+ fi, err := os.Stat(fullFilePath)
if err != nil {
- res = append(res, cc.NewErrReply(t, "Cannot delete file "+fileName+" because it does not exist or cannot be found."))
+ res = append(res, cc.NewErrReply(t, "Cannot delete file "+string(fileName)+" because it does not exist or cannot be found."))
return res, nil
}
switch mode := fi.Mode(); {
}
}
- if err := os.RemoveAll(path); err != nil {
+ if err := os.RemoveAll(fullFilePath); err != nil {
return res, err
}
func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
fileName := t.GetField(fieldFileName).Data
- filePath := ReadFilePath(t.GetField(fieldFilePath).Data)
+ filePath := t.GetField(fieldFilePath).Data
- ffo, err := NewFlattenedFileObject(cc.Server.Config.FileRoot+filePath, string(fileName))
+ var fp FilePath
+ err = fp.UnmarshalBinary(filePath)
+ if err != nil {
+ return res, err
+ }
+
+ ffo, err := NewFlattenedFileObject(cc.Server.Config.FileRoot, filePath, fileName)
if err != nil {
return res, err
}
transactionRef := cc.Server.NewTransactionRef()
data := binary.BigEndian.Uint32(transactionRef)
- cc.Server.Logger.Infow("File download", "path", filePath)
-
ft := &FileTransfer{
FileName: fileName,
- FilePath: []byte(filePath),
+ FilePath: filePath,
ReferenceNumber: transactionRef,
Type: FileDownload,
}
return res, err
}
- fullFilePath := fmt.Sprintf("%v%v", cc.Server.Config.FileRoot+fp.String(), string(fileTransfer.FileName))
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFilePath).Data, t.GetField(fieldFileName).Data)
+
transferSize, err := CalcTotalSize(fullFilePath)
if err != nil {
return res, err
}
func HandleGetFileNameList(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
- filePath := cc.Server.Config.FileRoot
-
- path := t.GetField(fieldFilePath).Data
- if len(path) > 0 {
- filePath = cc.Server.Config.FileRoot + ReadFilePath(path)
+ fullPath, err := readPath(
+ cc.Server.Config.FileRoot,
+ t.GetField(fieldFilePath).Data,
+ nil,
+ )
+ if err != nil {
+ return res, err
}
- fileNames, err := getFileNameList(filePath)
+ fileNames, err := getFileNameList(fullPath)
if err != nil {
return res, err
}
ID: &[]byte{0x00, 0x01},
Server: &Server{
Config: &Config{
- FileRoot: "./test/config/Files/",
+ FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
},
},
},
tranGetFileInfo, nil,
NewField(fieldFileName, []byte("testfile.txt")),
NewField(fieldFilePath, []byte{0x00, 0x00}),
- //NewField(fieldFilePath, []byte{
- // 0x00, 0x03,
- // 0x00, 0x00,
- // 0x04,
- // 0x74, 0x65, 0x73, 0x74,
- // 0x00, 0x00,
- // 0x06,
- // 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
- //
- // 0x00, 0x00,
- // 0x05,
- // 0x46, 0x69, 0x6c, 0x65, 73},
- //),
),
},
wantRes: []Transaction{
t: NewTransaction(
tranNewFolder, &[]byte{0, 1},
NewField(fieldFileName, []byte("../../testFolder")),
-
),
},
setup: func() {
ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
ErrorCode: []byte{0, 0, 0, 0},
},
- }, wantErr: false,
+ }, wantErr: false,
},
{
name: "fieldFilePath does not allow directory traversal",
ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1)
ErrorCode: []byte{0, 0, 0, 0},
},
- }, wantErr: false,
+ }, wantErr: false,
},
}
for _, tt := range tests {
}
}
+func TestHandleUploadFile(t *testing.T) {
+ type args struct {
+ cc *ClientConn
+ t *Transaction
+ }
+ tests := []struct {
+ name string
+ args args
+ wantRes []Transaction
+ wantErr bool
+ }{
+ {
+ name: "when request is valid",
+ args: args{
+ cc: &ClientConn{
+ Server: &Server{
+ FileTransfers: map[uint32]*FileTransfer{},
+ },
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessUploadFile)
+ access := bits[:]
+ return &access
+ }(),
+ },
+ },
+ t: NewTransaction(
+ tranUploadFile, &[]byte{0, 1},
+ NewField(fieldFileName, []byte("testFile")),
+ NewField(fieldFilePath, []byte{
+ 0x00, 0x01,
+ 0x00, 0x00,
+ 0x03,
+ 0x2e, 0x2e, 0x2f,
+ }),
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0xcb},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 0},
+ Fields: []Field{
+ NewField(fieldRefNum, []byte{0x52, 0xfd, 0xfc, 0x07}), // rand.Seed(1)
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "when user does not have required access",
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ access := bits[:]
+ return &access
+ }(),
+ },
+ Server: &Server{
+ FileTransfers: map[uint32]*FileTransfer{},
+ },
+ },
+ t: NewTransaction(
+ tranUploadFile, &[]byte{0, 1},
+ NewField(fieldFileName, []byte("testFile")),
+ NewField(fieldFilePath, []byte{
+ 0x00, 0x01,
+ 0x00, 0x00,
+ 0x03,
+ 0x2e, 0x2e, 0x2f,
+ }),
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0x00},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 1},
+ Fields: []Field{
+ NewField(fieldError, []byte("You are not allowed to upload files.")), // rand.Seed(1)
+ },
+ },
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ rand.Seed(1)
+ gotRes, err := HandleUploadFile(tt.args.cc, tt.args.t)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("HandleUploadFile() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if !tranAssertEqual(t, tt.wantRes, gotRes) {
+ t.Errorf("HandleUploadFile() gotRes = %v, want %v", gotRes, tt.wantRes)
+ }
+ })
+ }
+}