+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)
+}