aboutsummaryrefslogtreecommitdiff
path: root/hotline
diff options
context:
space:
mode:
authorJeff Halter <868228+jhalter@users.noreply.github.com>2022-05-28 11:12:00 -0700
committerJeff Halter <868228+jhalter@users.noreply.github.com>2022-05-28 11:12:00 -0700
commitdecc2fbf5db4a05aec93462ad35d890930bddd04 (patch)
tree28b15b1cb67eb298657c918faf1a3324bf6da424 /hotline
parent7f12122f861fab4f4d0e3972ca0e66ff8becae19 (diff)
Implement Make Alias transaction
Diffstat (limited to 'hotline')
-rw-r--r--hotline/access.go2
-rw-r--r--hotline/file_store.go11
-rw-r--r--hotline/transaction.go4
-rw-r--r--hotline/transaction_handlers.go48
-rw-r--r--hotline/transaction_handlers_test.go186
5 files changed, 248 insertions, 3 deletions
diff --git a/hotline/access.go b/hotline/access.go
index e2076ad..1cdbeed 100644
--- a/hotline/access.go
+++ b/hotline/access.go
@@ -40,7 +40,7 @@ const (
//accessSetFileComment = 28
//accessSetFolderComment = 29
//accessViewDropBoxes = 30
- //accessMakeAlias = 31
+ accessMakeAlias = 31
accessBroadcast = 32
accessNewsDeleteArt = 33
accessNewsCreateCat = 34
diff --git a/hotline/file_store.go b/hotline/file_store.go
index b56d9a1..c1c3929 100644
--- a/hotline/file_store.go
+++ b/hotline/file_store.go
@@ -11,6 +11,8 @@ type FileStore interface {
Mkdir(name string, perm os.FileMode) error
Stat(name string) (os.FileInfo, error)
Open(name string) (*os.File, error)
+ Symlink(oldname, newname string) error
+
// TODO: implement these
//Rename(oldpath string, newpath string) error
//RemoveAll(path string) error
@@ -30,6 +32,10 @@ func (fs OSFileStore) Open(name string) (*os.File, error) {
return os.Open(name)
}
+func (fs OSFileStore) Symlink(oldname, newname string) error {
+ return os.Symlink(oldname, newname)
+}
+
type MockFileStore struct {
mock.Mock
}
@@ -52,3 +58,8 @@ func (mfs MockFileStore) Open(name string) (*os.File, error) {
args := mfs.Called(name)
return args.Get(0).(*os.File), args.Error(1)
}
+
+func (mfs MockFileStore) Symlink(oldname, newname string) error {
+ args := mfs.Called(oldname, newname)
+ return args.Error(0)
+}
diff --git a/hotline/transaction.go b/hotline/transaction.go
index 835d194..2994785 100644
--- a/hotline/transaction.go
+++ b/hotline/transaction.go
@@ -40,8 +40,8 @@ const (
tranGetFileInfo = 206
tranSetFileInfo = 207
tranMoveFile = 208
- // tranMakeFileAlias = 209 TODO: implement file alias command
- tranDownloadFldr = 210
+ tranMakeFileAlias = 209 // TODO: implement file alias command
+ tranDownloadFldr = 210
// tranDownloadInfo = 211 TODO: implement file transfer queue
// tranDownloadBanner = 212 TODO: figure out what this is used for
tranUploadFldr = 213
diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go
index 7e3afa1..ade846e 100644
--- a/hotline/transaction_handlers.go
+++ b/hotline/transaction_handlers.go
@@ -256,6 +256,16 @@ var TransactionHandlers = map[uint16]TransactionType{
Name: "tranSetChatSubject",
Handler: HandleSetChatSubject,
},
+ tranMakeFileAlias: {
+ Access: accessAlwaysAllow,
+ Name: "tranMakeFileAlias",
+ Handler: HandleMakeAlias,
+ RequiredFields: []requiredField{
+ {ID: fieldFileName, minLen: 1},
+ {ID: fieldFilePath, minLen: 1},
+ {ID: fieldFileNewPath, minLen: 1},
+ },
+ },
tranSetClientUserInfo: {
Access: accessAlwaysAllow,
Name: "tranSetClientUserInfo",
@@ -1609,3 +1619,41 @@ func HandleSetChatSubject(cc *ClientConn, t *Transaction) (res []Transaction, er
return res, err
}
+
+// HandleMakeAlias makes a file alias using the specified path.
+// Fields used in the request:
+// 201 File name
+// 202 File path
+// 212 File new path Destination path
+//
+// Fields used in the reply:
+// None
+func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+ if !authorize(cc.Account.Access, accessMakeAlias) {
+ res = append(res, cc.NewErrReply(t, "You are not allowed to make aliases."))
+ return res, err
+ }
+ fileName := t.GetField(fieldFileName).Data
+ filePath := t.GetField(fieldFilePath).Data
+ fileNewPath := t.GetField(fieldFileNewPath).Data
+
+ fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+ if err != nil {
+ return res, err
+ }
+
+ fullNewFilePath, err := readPath(cc.Server.Config.FileRoot, fileNewPath, fileName)
+ if err != nil {
+ return res, err
+ }
+
+ cc.Server.Logger.Debugw("Make alias", "src", fullFilePath, "dst", fullNewFilePath)
+
+ if err := FS.Symlink(fullFilePath, fullNewFilePath); err != nil {
+ res = append(res, cc.NewErrReply(t, "Error creating alias"))
+ return res, nil
+ }
+
+ res = append(res, cc.NewReply(t))
+ return res, err
+}
diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go
index 7c5ee43..a83f4cc 100644
--- a/hotline/transaction_handlers_test.go
+++ b/hotline/transaction_handlers_test.go
@@ -1,10 +1,12 @@
package hotline
import (
+ "errors"
"github.com/stretchr/testify/assert"
"io/fs"
"math/rand"
"os"
+ "strings"
"testing"
)
@@ -855,3 +857,187 @@ func TestHandleUploadFile(t *testing.T) {
})
}
}
+
+func TestHandleMakeAlias(t *testing.T) {
+ type args struct {
+ cc *ClientConn
+ t *Transaction
+ }
+ tests := []struct {
+ name string
+ setup func()
+ args args
+ wantRes []Transaction
+ wantErr bool
+ }{
+ {
+ name: "with valid input and required permissions",
+ setup: func() {
+ mfs := MockFileStore{}
+ path, _ := os.Getwd()
+ mfs.On(
+ "Symlink",
+ path+"/test/config/Files/foo/testFile",
+ path+"/test/config/Files/bar/testFile",
+ ).Return(nil)
+ FS = mfs
+ },
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessMakeAlias)
+ access := bits[:]
+ return &access
+ }(),
+ },
+ Server: &Server{
+ Config: &Config{
+ FileRoot: func() string {
+ path, _ := os.Getwd()
+ return path + "/test/config/Files"
+ }(),
+ },
+ Logger: NewTestLogger(),
+ },
+ },
+ t: NewTransaction(
+ tranMakeFileAlias, &[]byte{0, 1},
+ NewField(fieldFileName, []byte("testFile")),
+ NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
+ NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
+ ),
+ },
+ wantRes: []Transaction{
+ {
+ Flags: 0x00,
+ IsReply: 0x01,
+ Type: []byte{0, 0xd1},
+ ID: []byte{0x9a, 0xcb, 0x04, 0x42},
+ ErrorCode: []byte{0, 0, 0, 0},
+ Fields: []Field(nil),
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "when symlink returns an error",
+ setup: func() {
+ mfs := MockFileStore{}
+ path, _ := os.Getwd()
+ mfs.On(
+ "Symlink",
+ path+"/test/config/Files/foo/testFile",
+ path+"/test/config/Files/bar/testFile",
+ ).Return(errors.New("ohno"))
+ FS = mfs
+ },
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ bits.Set(accessMakeAlias)
+ access := bits[:]
+ return &access
+ }(),
+ },
+ Server: &Server{
+ Config: &Config{
+ FileRoot: func() string {
+ path, _ := os.Getwd()
+ return path + "/test/config/Files"
+ }(),
+ },
+ Logger: NewTestLogger(),
+ },
+ },
+ t: NewTransaction(
+ tranMakeFileAlias, &[]byte{0, 1},
+ NewField(fieldFileName, []byte("testFile")),
+ NewField(fieldFilePath, EncodeFilePath(strings.Join([]string{"foo"}, "/"))),
+ NewField(fieldFileNewPath, EncodeFilePath(strings.Join([]string{"bar"}, "/"))),
+ ),
+ },
+ 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("Error creating alias")),
+ },
+ },
+ },
+ wantErr: false,
+ },
+ {
+ name: "when user does not have required permission",
+ setup: func() {},
+ args: args{
+ cc: &ClientConn{
+ Account: &Account{
+ Access: func() *[]byte {
+ var bits accessBitmap
+ access := bits[:]
+ return &access
+ }(),
+ },
+ Server: &Server{
+ Config: &Config{
+ FileRoot: func() string {
+ path, _ := os.Getwd()
+ return path + "/test/config/Files"
+ }(),
+ },
+ },
+ },
+ t: NewTransaction(
+ tranMakeFileAlias, &[]byte{0, 1},
+ NewField(fieldFileName, []byte("testFile")),
+ NewField(fieldFilePath, []byte{
+ 0x00, 0x01,
+ 0x00, 0x00,
+ 0x03,
+ 0x2e, 0x2e, 0x2e,
+ }),
+ NewField(fieldFileNewPath, []byte{
+ 0x00, 0x01,
+ 0x00, 0x00,
+ 0x03,
+ 0x2e, 0x2e, 0x2e,
+ }),
+ ),
+ },
+ 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 make aliases.")),
+ },
+ },
+ },
+ wantErr: false,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ tt.setup()
+
+ gotRes, err := HandleMakeAlias(tt.args.cc, tt.args.t)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("HandleMakeAlias(%v, %v)", tt.args.cc, tt.args.t)
+ return
+ }
+
+ tranAssertEqual(t, tt.wantRes, gotRes)
+ })
+ }
+}