From 72c6ddc104c349a4798fddeaa8f77dbe139fe104 Mon Sep 17 00:00:00 2001 From: Jeff Halter <868228+jhalter@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:41:12 -0700 Subject: Fix Mac Roman decoding incorrectly applied to filesystem root paths ReadPath() and HandleNewFolder decoded the entire joined path from Mac Roman, including the fileRoot prefix which is already UTF-8. This caused file listing failures when config paths or FileRoot values contained non-ASCII characters. Now only client-provided path components (subPath, fileName/folderName) are decoded before joining with fileRoot. --- hotline/file_path.go | 18 +++++++++++++----- hotline/file_path_test.go | 23 +++++++++++++++++++++++ internal/mobius/transaction_handlers.go | 12 ++++++++++-- 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/hotline/file_path.go b/hotline/file_path.go index a3a13f1..c97faea 100644 --- a/hotline/file_path.go +++ b/hotline/file_path.go @@ -115,14 +115,22 @@ func ReadPath(fileRoot string, filePath, fileName []byte) (fullPath string, err subPath = path.Join("/", subPath, string(pathItem.Name)) } + // Decode only client-provided path components from Mac Roman to UTF-8. + // The fileRoot is already a UTF-8 filesystem path and must not be decoded. + subPath, err = txtDecoder.String(subPath) + if err != nil { + return "", fmt.Errorf("invalid filepath encoding: %w", err) + } + + decodedFileName, err := txtDecoder.String(string(fileName)) + if err != nil { + return "", fmt.Errorf("invalid filename encoding: %w", err) + } + fullPath = path.Join( fileRoot, subPath, - path.Join("/", string(fileName)), + path.Join("/", decodedFileName), ) - fullPath, err = txtDecoder.String(fullPath) - if err != nil { - return "", fmt.Errorf("invalid filepath encoding: %w", err) - } return fullPath, nil } diff --git a/hotline/file_path_test.go b/hotline/file_path_test.go index ee0cbac..c86a8d9 100644 --- a/hotline/file_path_test.go +++ b/hotline/file_path_test.go @@ -152,6 +152,29 @@ func Test_readPath(t *testing.T) { }, want: "/usr/local/var/mobius/Files", }, + { + name: "when fileRoot contains non-ASCII UTF-8 characters", + args: args{ + fileRoot: "/files/español", + filePath: nil, + fileName: []byte("foo"), + }, + want: "/files/español/foo", + }, + { + name: "when fileRoot contains non-ASCII and filePath has Mac Roman bytes", + args: args{ + fileRoot: "/files/español", + filePath: []byte{ + 0x00, 0x01, + 0x00, 0x00, + 0x06, + 0x63, 0x61, 0x66, 0x8e, 0x73, 0x21, // "caf\x8es!" where 0x8e is Mac Roman é + }, + fileName: []byte("foo"), + }, + want: "/files/español/cafés!/foo", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/mobius/transaction_handlers.go b/internal/mobius/transaction_handlers.go index 259ac02..33af941 100644 --- a/internal/mobius/transaction_handlers.go +++ b/internal/mobius/transaction_handlers.go @@ -568,11 +568,19 @@ func HandleNewFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotl subPath = path.Join("/", subPath, string(pathItem.Name)) } } - newFolderPath := path.Join(cc.FileRoot(), subPath, folderName) - newFolderPath, err := txtDecoder.String(newFolderPath) + + // Decode only client-provided path components from Mac Roman to UTF-8. + // The FileRoot is already a UTF-8 filesystem path and must not be decoded. + subPath, err := txtDecoder.String(subPath) if err != nil { return res } + folderName, err = txtDecoder.String(folderName) + if err != nil { + return res + } + + newFolderPath := path.Join(cc.FileRoot(), subPath, folderName) // TODO: check path and folder Name lengths -- cgit