11 "github.com/jhalter/mobius/hotline"
14 // ResolveUserPath processes paths for the special `~` directory.
15 func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) {
16 if !strings.HasPrefix(requestedPath, "~") {
17 return requestedPath, nil
20 userFolder := filepath.Join("~", cc.Account.Login)
21 actualUserFolder := filepath.Join(cc.FileRoot(), userFolder)
22 if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() {
23 return strings.Replace(requestedPath, "~", userFolder, 1), nil
26 return "", fmt.Errorf("user folder does not exist")
29 // updateTransactionPath updates the FieldFilePath in a transaction in-place.
30 func updateTransactionPath(t *hotline.Transaction, newPath string) {
31 for i, field := range t.Fields {
32 if field.Type == hotline.FieldFilePath {
33 if encodedPath, err := txtEncoder.String(newPath); err == nil {
34 if fpBytes, err := encodeFilePath(encodedPath); err == nil {
35 t.Fields[i].Data = fpBytes
43 // encodeFilePath encodes a string path into hotline.FilePath binary format.
44 func encodeFilePath(path string) ([]byte, error) {
45 components := strings.Split(path, string(filepath.Separator))
46 var fp hotline.FilePath
47 binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
49 var buffer bytes.Buffer
50 if err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount); err != nil {
54 for _, component := range components {
55 if component == "" || len(component) > 255 {
56 return nil, fmt.Errorf("invalid file path component")
58 encodedComponent, err := txtEncoder.String(component)
62 buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))})
63 buffer.Write([]byte(encodedComponent))
66 return buffer.Bytes(), nil
69 // handleFileTransaction handles various file operations that require resolving `~`
70 func handleFileTransaction(cc *hotline.ClientConn, t *hotline.Transaction, handler func(*hotline.ClientConn, *hotline.Transaction) []hotline.Transaction, errMsg string) []hotline.Transaction {
71 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
76 sliceLen := min(len(cc.FileRoot()) + 1, len(requestedPath))
77 resolvedPath, err := ResolveUserPath(cc, requestedPath[sliceLen:])
79 return cc.NewErrReply(t, errMsg)
82 updateTransactionPath(t, resolvedPath)
86 // File operation handlers
87 func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
88 return handleFileTransaction(cc, t, HandleUploadFile, "Cannot upload to non-existent user folder.")
91 func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
92 return handleFileTransaction(cc, t, HandleUploadFolder, "Cannot upload to non-existent user folder.")
95 func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
96 return handleFileTransaction(cc, t, HandleDeleteFile, "Cannot delete non-existent file.")
99 func HandleDownloadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
100 return handleFileTransaction(cc, t, HandleDownloadFile, "Cannot download non-existent user file.")
103 func HandleDownloadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
104 return handleFileTransaction(cc, t, HandleDownloadFolder, "Cannot download non-existent user folder.")
107 // HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
108 func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
109 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
114 if requestedPath == cc.FileRoot() {
115 fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
120 userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
121 if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
122 filteredFiles := make([]hotline.Field, 0, len(fileNames))
123 for _, file := range fileNames {
124 if !strings.Contains(string(file.Data), "~") {
125 filteredFiles = append(filteredFiles, file)
128 return []hotline.Transaction{cc.NewReply(t, filteredFiles...)}
130 return []hotline.Transaction{cc.NewReply(t, fileNames...)}
133 if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
134 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
138 updateTransactionPath(t, resolvedPath)
139 return HandleGetFileNameList(cc, t)
142 return HandleGetFileNameList(cc, t)