package mobius import ( "bytes" "encoding/binary" "fmt" "os" "path/filepath" "strings" "github.com/jhalter/mobius/hotline" ) // ResolveUserPath processes paths for the special `~` directory. // If the requested path starts with `~`, it returns the user's corresponding folder. func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) { if strings.HasPrefix(requestedPath, "~") { userFolder := filepath.Join("~", cc.Account.Login) actualUserFolder := filepath.Join(cc.FileRoot(), userFolder) if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() { return strings.Replace(requestedPath, "~", userFolder, 1), nil } else { return "", fmt.Errorf("user folder does not exist") } } return requestedPath, nil } // updateTransactionPath updates the FieldFilePath in a transaction in-place. func updateTransactionPath(t *hotline.Transaction, newPath string) { for i, field := range t.Fields { if field.Type == hotline.FieldFilePath { // Convert newPath to the correct binary format encodedPath, err := txtEncoder.String(newPath) if err != nil { return // Encoding failure, don't update } // Convert the new path into the correct binary format for FilePath fpBytes, err := encodeFilePath(encodedPath) if err != nil { return } // Assign correctly formatted binary path t.Fields[i].Data = fpBytes return } } } // Encode a string path into hotline.FilePath binary format func encodeFilePath(path string) ([]byte, error) { components := strings.Split(path, string(filepath.Separator)) var fp hotline.FilePath // Set the item count binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components))) var buffer bytes.Buffer err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount) if err != nil { return nil, err } for _, component := range components { if component == "" { continue } if len(component) > 255 { // Ensure component size is within range return nil, fmt.Errorf("file path component too long") } // Convert to MacRoman encoding encodedComponent, err := txtEncoder.String(component) if err != nil { return nil, err } // Write length and name buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))}) // Leading bytes buffer.Write([]byte(encodedComponent)) } return buffer.Bytes(), nil } // HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~` func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil) if err != nil { return res } if requestedPath == cc.FileRoot() { fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles) if err != nil { return res } userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login) if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() { filteredFiles := []hotline.Field{} for _, file := range fileNames { if !strings.Contains(string(file.Data), "~") { filteredFiles = append(filteredFiles, file) } } return append(res, cc.NewReply(t, filteredFiles...)) } return append(res, cc.NewReply(t, fileNames...)) } if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) { resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:]) if err != nil { return res } updateTransactionPath(t, resolvedPath) return HandleGetFileNameList(cc, t) } return HandleGetFileNameList(cc, t) } // HandleUploadFileWithUserFolders ensures uploads go to the correct user folder when using `~`. func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil) if err != nil { return res } resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:]) if err != nil { return cc.NewErrReply(t, "Cannot upload to non-existent user folder.") } updateTransactionPath(t, resolvedPath) return HandleUploadFile(cc, t) } // HandleUploadFolderWithUserFolders ensures directory uploads go to the correct user folder when using `~`. func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil) if err != nil { return res } resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:]) if err != nil { return cc.NewErrReply(t, "Cannot upload to non-existent user folder.") } updateTransactionPath(t, resolvedPath) return HandleUploadFolder(cc, t) } func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil) if err != nil { return res } resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:]) if err != nil { return cc.NewErrReply(t, "Cannot upload to non-existent user folder.") } updateTransactionPath(t, resolvedPath) return HandleUploadFolder(cc, t) }