From f78ab940a8c09702be3e0ba310ac56e02fa76701 Mon Sep 17 00:00:00 2001 From: Jeff Halter <868228+jhalter@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:54:44 -0700 Subject: Add configurable text encoding for file and folder names Replace hardcoded Mac Roman encoding globals with a configurable Encoding field in config.yaml. Servers that exclusively serve modern UTF-8 clients can now set Encoding: utf8 to disable Mac Roman conversion. The default remains "macintosh" for backward compatibility. - Add Encoding field to Config struct (macintosh|utf8) - Store TextDecoder/TextEncoder on Server, initialized from config - Add TextDecoder()/TextEncoder() accessors on ClientConn - Pass decoder/encoder explicitly to ReadPath and GetFileNameList - Remove package-level txtEncoder/txtDecoder globals - Add warning log when files are skipped due to encoding errors - Log and return error replies in HandleGetFileNameList on failure - Document the new config option in docs/text-encoding.md --- .../mobius/config/config.yaml | 7 + docs/text-encoding.md | 70 +++++++++ hotline/client_conn.go | 4 + hotline/config.go | 1 + hotline/file_path.go | 10 +- hotline/file_path_test.go | 54 ++++++- hotline/files.go | 8 +- hotline/files_test.go | 49 ++++++ hotline/server.go | 22 ++- hotline/server_test.go | 54 +++++++ internal/mobius/transaction_handlers.go | 51 +++---- internal/mobius/transaction_handlers_test.go | 165 ++++++++++++++++++++- 12 files changed, 449 insertions(+), 46 deletions(-) create mode 100644 docs/text-encoding.md diff --git a/cmd/mobius-hotline-server/mobius/config/config.yaml b/cmd/mobius-hotline-server/mobius/config/config.yaml index 71b7e65..d7ded22 100644 --- a/cmd/mobius-hotline-server/mobius/config/config.yaml +++ b/cmd/mobius-hotline-server/mobius/config/config.yaml @@ -25,6 +25,13 @@ Trackers: - hotline.kicks-ass.net:5499 # - example-tracker-with-password.com:5499:mypassword +# Text encoding used for file and folder names sent to and from clients. +# Supported values: +# "macintosh" - Translates between Mac Roman and UTF-8 (default, compatible with classic Hotline clients) +# "utf8" - No conversion; passes file names through as-is (for servers with only modern UTF-8 clients) +# If omitted, defaults to "macintosh". +Encoding: macintosh + # Preserve resource forks and file type/creator codes for files uploaded by Macintosh clients. # This comes with trade-offs. For more details, see: # https://github.com/jhalter/mobius/wiki/Resource-fork-support-in-Mobius diff --git a/docs/text-encoding.md b/docs/text-encoding.md new file mode 100644 index 0000000..71bdada --- /dev/null +++ b/docs/text-encoding.md @@ -0,0 +1,70 @@ +# Text Encoding + +## Background + +The Hotline protocol was designed for classic Mac OS, which used an encoding called Mac Roman for text. Modern operating systems use UTF-8 instead. Mobius automatically translates between these two encodings so that classic Hotline clients and modern filesystems can work together. + +By default, Mobius assumes all clients use Mac Roman encoding. If your server exclusively serves modern UTF-8 clients, you can disable the Mac Roman conversion. This document explains how encoding works and how to configure it. + +## How It Works + +### File and folder names are translated + +When a Hotline client uploads, downloads, browses, creates, or renames files and folders, Mobius translates the names between Mac Roman and UTF-8: + +- **Client to server**: File and folder names arriving from clients are converted from Mac Roman to UTF-8 before being written to the filesystem. +- **Server to client**: File and folder names read from the filesystem are converted from UTF-8 to Mac Roman before being sent to clients. + +This means files on disk always use UTF-8 names, regardless of what encoding the client uses. You can place files with Unicode names in the server's file directory and clients will see them — as long as the characters have Mac Roman equivalents. + +### Chat, news, and usernames are NOT translated + +Text in chat messages, news articles, usernames, and private messages is passed through as raw bytes with no encoding conversion. This means: + +- If all your users are on classic Mac clients, they'll see each other's text correctly (all Mac Roman). +- If all your users are on modern UTF-8 clients, they'll also see each other's text correctly. +- If you have a mix of classic and modern clients, users may occasionally see garbled characters in chat and news from users on a differently-encoded client. + +There is no way to configure this behavior — the Hotline protocol has no mechanism for clients to declare what encoding they use. + +## Configuration + +The `Encoding` field in `config.yaml` controls how file and folder names are translated: + +```yaml +# Default — translates between Mac Roman and UTF-8 (compatible with classic Hotline clients) +Encoding: macintosh + +# No-op — passes file names through without conversion (for servers with only modern UTF-8 clients) +Encoding: utf8 +``` + +If `Encoding` is omitted, it defaults to `macintosh`, preserving backward compatibility with existing configurations. + +**Impact of `utf8` on classic Mac clients**: With `Encoding: utf8`, the server no longer translates between Mac Roman and UTF-8. Classic Hotline clients send file and folder names encoded as Mac Roman, and with this setting those bytes are stored on disk without conversion. This means non-ASCII characters (accented letters, curly quotes, etc.) will be written to the filesystem as their raw Mac Roman byte values, producing mojibake in file names when viewed on the host OS. Going the other direction, UTF-8 file names on disk will be sent to classic clients without conversion, so any characters that differ between UTF-8 and Mac Roman will display incorrectly on the client side. **Only use `utf8` if you are confident that no classic Mac clients will connect to your server.** + +## Practical Implications + +### Server file root path + +The server's configured file root path (the directory that holds your shared files) can safely contain non-ASCII characters, such as `/srv/données` or `/home/café/files`. Only the client-provided portion of file paths is subject to encoding translation — the server root path is treated as native UTF-8. + +### Characters outside Mac Roman + +Mac Roman supports 256 characters, covering most Western European languages. If a file on disk has a name containing characters outside the Mac Roman set (e.g., Chinese, Japanese, Cyrillic, or certain symbols), those characters cannot be represented when sent to clients. They will be replaced with a fallback character. + +### Mixing client types + +Modern Hotline clients that send UTF-8 for file operations may produce unexpected file names on disk, since the server assumes all clients send Mac Roman. If you primarily serve modern clients, consider setting `Encoding: utf8` in your config to disable the Mac Roman conversion. + +## Summary + +| What | Encoding translation? | Notes | +|---------------------------|----------------------|-----------------------------------------------| +| File and folder names | Yes | Mac Roman <-> UTF-8 at the filesystem boundary | +| Chat messages | No | Raw bytes, passed through as-is | +| News articles and titles | No | Raw bytes, passed through as-is | +| Usernames | No | Raw bytes, passed through as-is | +| Private messages | No | Raw bytes, passed through as-is | +| File comments | No | Stored and retrieved as raw bytes | +| Login credentials | No | Obfuscated, no charset conversion | diff --git a/hotline/client_conn.go b/hotline/client_conn.go index d8fd435..5428ea5 100644 --- a/hotline/client_conn.go +++ b/hotline/client_conn.go @@ -11,6 +11,7 @@ import ( "sync" "golang.org/x/crypto/bcrypt" + "golang.org/x/text/encoding" ) var clientConnSortFunc = func(a, b *ClientConn) int { @@ -44,6 +45,9 @@ type ClientConn struct { mu sync.RWMutex } +func (cc *ClientConn) TextDecoder() *encoding.Decoder { return cc.Server.TextDecoder } +func (cc *ClientConn) TextEncoder() *encoding.Encoder { return cc.Server.TextEncoder } + func (cc *ClientConn) FileRoot() string { if cc.Account.FileRoot != "" { return cc.Account.FileRoot diff --git a/hotline/config.go b/hotline/config.go index d918ecb..8b6adff 100644 --- a/hotline/config.go +++ b/hotline/config.go @@ -15,4 +15,5 @@ type Config struct { PreserveResourceForks bool `yaml:"PreserveResourceForks"` // Enable preservation of file info and resource forks in sidecar files IgnoreFiles []string `yaml:"IgnoreFiles"` // List of regular expression for filtering files from the file list EnableBonjour bool `yaml:"EnableBonjour"` // Enable service announcement on local network with Bonjour + Encoding string `yaml:"Encoding" validate:"omitempty,oneof=macintosh utf8"` // Text encoding for client communication } diff --git a/hotline/file_path.go b/hotline/file_path.go index c97faea..6bd538f 100644 --- a/hotline/file_path.go +++ b/hotline/file_path.go @@ -9,6 +9,8 @@ import ( "io" "path" "strings" + + "golang.org/x/text/encoding" ) // FilePathItem represents the file or directory portion of a delimited file path (e.g. foo and bar in "/foo/bar") @@ -102,7 +104,7 @@ func (fp *FilePath) Len() uint16 { return binary.BigEndian.Uint16(fp.ItemCount[:]) } -func ReadPath(fileRoot string, filePath, fileName []byte) (fullPath string, err error) { +func ReadPath(fileRoot string, filePath, fileName []byte, decoder *encoding.Decoder) (fullPath string, err error) { var fp FilePath if filePath != nil { if _, err = fp.Write(filePath); err != nil { @@ -115,14 +117,14 @@ 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. + // Decode only client-provided path components using the configured text encoding. // The fileRoot is already a UTF-8 filesystem path and must not be decoded. - subPath, err = txtDecoder.String(subPath) + subPath, err = decoder.String(subPath) if err != nil { return "", fmt.Errorf("invalid filepath encoding: %w", err) } - decodedFileName, err := txtDecoder.String(string(fileName)) + decodedFileName, err := decoder.String(string(fileName)) if err != nil { return "", fmt.Errorf("invalid filename encoding: %w", err) } diff --git a/hotline/file_path_test.go b/hotline/file_path_test.go index c86a8d9..9fe82f5 100644 --- a/hotline/file_path_test.go +++ b/hotline/file_path_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" ) func TestFilePath_Write(t *testing.T) { @@ -178,7 +180,7 @@ func Test_readPath(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ReadPath(tt.args.fileRoot, tt.args.filePath, tt.args.fileName) + got, err := ReadPath(tt.args.fileRoot, tt.args.filePath, tt.args.fileName, charmap.Macintosh.NewDecoder()) if (err != nil) != tt.wantErr { t.Errorf("ReadPath() error = %v, wantErr %v", err, tt.wantErr) return @@ -190,6 +192,56 @@ func Test_readPath(t *testing.T) { } } +func Test_readPath_NopDecoder(t *testing.T) { + type args struct { + fileRoot string + filePath []byte + fileName []byte + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Mac Roman bytes pass through unchanged with Nop decoder", + args: args{ + fileRoot: "/files", + filePath: []byte{ + 0x00, 0x01, + 0x00, 0x00, + 0x06, + 0x63, 0x61, 0x66, 0x8e, 0x73, 0x21, // "caf\x8es!" where 0x8e is Mac Roman é + }, + fileName: []byte("foo"), + }, + // With Nop decoder, 0x8e is NOT converted to UTF-8 é — it passes through as the raw byte. + want: "/files/caf\x8es!/foo", + }, + { + name: "plain ASCII is unchanged with Nop decoder", + args: args{ + fileRoot: "/files", + filePath: nil, + fileName: []byte("hello.txt"), + }, + want: "/files/hello.txt", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ReadPath(tt.args.fileRoot, tt.args.filePath, tt.args.fileName, encoding.Nop.NewDecoder()) + if err != nil { + t.Errorf("ReadPath() error = %v", err) + return + } + if got != tt.want { + t.Errorf("ReadPath() got = %q, want %q", got, tt.want) + } + }) + } +} + func Test_fileItemScanner(t *testing.T) { type args struct { data []byte diff --git a/hotline/files.go b/hotline/files.go index 581b11c..a5b3441 100644 --- a/hotline/files.go +++ b/hotline/files.go @@ -6,10 +6,13 @@ import ( "fmt" "io" "io/fs" + "log/slog" "os" "path/filepath" "regexp" "strings" + + "golang.org/x/text/encoding" ) func FileTypeFromFilename(filename string) fileType { @@ -34,7 +37,7 @@ func fileTypeFromInfo(info fs.FileInfo) (ft fileType, err error) { const maxFileSize = 4294967296 -func GetFileNameList(path string, ignoreList []string) (fields []Field, err error) { +func GetFileNameList(path string, ignoreList []string, encoder *encoding.Encoder, logger *slog.Logger) (fields []Field, err error) { files, err := os.ReadDir(path) if err != nil { return fields, fmt.Errorf("error reading path: %s: %w", path, err) @@ -123,8 +126,9 @@ func GetFileNameList(path string, ignoreList []string) (fields []Field, err erro } strippedName := strings.ReplaceAll(file.Name(), ".incomplete", "") - strippedName, err = txtEncoder.String(strippedName) + strippedName, err = encoder.String(strippedName) if err != nil { + logger.Warn("skipping file with unencodable name", "name", file.Name(), "err", err) continue } diff --git a/hotline/files_test.go b/hotline/files_test.go index 3070313..3d9846f 100644 --- a/hotline/files_test.go +++ b/hotline/files_test.go @@ -3,11 +3,14 @@ package hotline import ( "bytes" "encoding/binary" + "log/slog" "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" ) func TestEncodeFilePath(t *testing.T) { @@ -170,3 +173,49 @@ func TestCalcItemCount(t *testing.T) { }) } } + +func TestGetFileNameList_Encoding(t *testing.T) { + // Create a temp dir with a file whose name contains a non-ASCII UTF-8 character. + tempDir, err := os.MkdirTemp("", "test-encoding") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer func() { _ = os.RemoveAll(tempDir) }() + + // "café.txt" — the é is UTF-8 on disk + if err := os.WriteFile(filepath.Join(tempDir, "café.txt"), []byte("hello"), 0644); err != nil { + t.Fatalf("Failed to create test file: %v", err) + } + + // FileNameWithInfoHeader is 20 bytes: Type[4] + Creator[4] + FileSize[4] + RSVD[4] + NameScript[2] + NameSize[2] + const headerLen = 20 + + extractName := func(fields []Field) string { + for _, f := range fields { + if f.Type == FieldFileNameWithInfo { + nameSize := binary.BigEndian.Uint16(f.Data[headerLen-2 : headerLen]) + return string(f.Data[headerLen : headerLen+nameSize]) + } + } + return "" + } + + t.Run("macintosh encoder converts UTF-8 to Mac Roman", func(t *testing.T) { + fields, err := GetFileNameList(tempDir, nil, charmap.Macintosh.NewEncoder(), slog.Default()) + assert.NoError(t, err) + assert.Len(t, fields, 1) + + name := extractName(fields) + // Mac Roman é is 0x8e, so "café.txt" becomes "caf\x8e.txt" + assert.Equal(t, "caf\x8e.txt", name) + }) + + t.Run("nop encoder passes UTF-8 through unchanged", func(t *testing.T) { + fields, err := GetFileNameList(tempDir, nil, encoding.Nop.NewEncoder(), slog.Default()) + assert.NoError(t, err) + assert.Len(t, fields, 1) + + name := extractName(fields) + assert.Equal(t, "café.txt", name) + }) +} diff --git a/hotline/server.go b/hotline/server.go index 545e84c..dcb90e7 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -18,6 +18,7 @@ import ( "time" "github.com/redis/go-redis/v9" + "golang.org/x/text/encoding" "golang.org/x/text/encoding/charmap" "golang.org/x/time/rate" ) @@ -30,12 +31,6 @@ type requestCtx struct { remoteAddr string } -// Converts bytes from Mac Roman encoding to UTF-8 -var txtDecoder = charmap.Macintosh.NewDecoder() - -// Converts bytes from UTF-8 to Mac Roman encoding -var txtEncoder = charmap.Macintosh.NewEncoder() - type Server struct { NetInterface string Port int @@ -73,6 +68,9 @@ type Server struct { // TrackerRegistrar handles tracker registration (injectable for testing) TrackerRegistrar TrackerRegistrar + TextDecoder *encoding.Decoder + TextEncoder *encoding.Encoder + TLSConfig *tls.Config TLSPort int } @@ -140,6 +138,16 @@ func NewServer(options ...Option) (*Server, error) { opt(&server) } + // Initialize text encoding based on config. + switch server.Config.Encoding { + case "utf8": + server.TextDecoder = encoding.Nop.NewDecoder() + server.TextEncoder = encoding.Nop.NewEncoder() + default: + server.TextDecoder = charmap.Macintosh.NewDecoder() + server.TextEncoder = charmap.Macintosh.NewEncoder() + } + // generate a new random passID for tracker registration _, err := rand.Read(server.TrackerPassID[:]) if err != nil { @@ -690,7 +698,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro "Name", string(fileTransfer.ClientConn.UserName), ) - fullPath, err := ReadPath(fileTransfer.FileRoot, fileTransfer.FilePath, fileTransfer.FileName) + fullPath, err := ReadPath(fileTransfer.FileRoot, fileTransfer.FilePath, fileTransfer.FileName, s.TextDecoder) if err != nil { return err } diff --git a/hotline/server_test.go b/hotline/server_test.go index db70480..abc2f9a 100644 --- a/hotline/server_test.go +++ b/hotline/server_test.go @@ -13,6 +13,8 @@ import ( "time" "github.com/stretchr/testify/assert" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" ) type mockReadWriter struct { @@ -177,6 +179,8 @@ func TestServer_handleFileTransfer(t *testing.T) { Logger: tt.fields.Logger, Stats: tt.fields.Stats, FS: tt.fields.FS, + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), } tt.wantErr(t, s.handleFileTransfer(tt.args.ctx, tt.args.rwc), fmt.Sprintf("handleFileTransfer(%v, %v)", tt.args.ctx, tt.args.rwc)) @@ -186,6 +190,56 @@ func TestServer_handleFileTransfer(t *testing.T) { } } +func TestNewServer_Encoding(t *testing.T) { + tests := []struct { + name string + encoding string + wantDecoder *encoding.Decoder + wantEncoder *encoding.Encoder + }{ + { + name: "default empty string uses Mac Roman", + encoding: "", + wantDecoder: charmap.Macintosh.NewDecoder(), + wantEncoder: charmap.Macintosh.NewEncoder(), + }, + { + name: "macintosh uses Mac Roman", + encoding: "macintosh", + wantDecoder: charmap.Macintosh.NewDecoder(), + wantEncoder: charmap.Macintosh.NewEncoder(), + }, + { + name: "utf8 uses Nop (pass-through)", + encoding: "utf8", + wantDecoder: encoding.Nop.NewDecoder(), + wantEncoder: encoding.Nop.NewEncoder(), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + srv, err := NewServer(WithConfig(Config{Encoding: tt.encoding})) + assert.NoError(t, err) + + // Verify encoder/decoder behavior by round-tripping a Mac Roman byte. + // 0x8e is Mac Roman for "é". + input := string([]byte{0x8e}) + + gotDec, err := srv.TextDecoder.String(input) + assert.NoError(t, err) + wantDec, err := tt.wantDecoder.String(input) + assert.NoError(t, err) + assert.Equal(t, wantDec, gotDec) + + gotEnc, err := srv.TextEncoder.String("é") + assert.NoError(t, err) + wantEnc, err := tt.wantEncoder.String("é") + assert.NoError(t, err) + assert.Equal(t, wantEnc, gotEnc) + }) + } +} + func TestParseTrackerPassword(t *testing.T) { tests := []struct { name string diff --git a/internal/mobius/transaction_handlers.go b/internal/mobius/transaction_handlers.go index 33af941..ce31abd 100644 --- a/internal/mobius/transaction_handlers.go +++ b/internal/mobius/transaction_handlers.go @@ -14,7 +14,6 @@ import ( "time" "github.com/jhalter/mobius/hotline" - "golang.org/x/text/encoding/charmap" ) // Public error message constants for reuse by other packages @@ -96,12 +95,6 @@ const ( ErrMsgReadMessageBoard = "Error reading message board." ) -// Converts bytes from Mac Roman encoding to UTF-8 -var txtDecoder = charmap.Macintosh.NewDecoder() - -// Converts bytes from UTF-8 to Mac Roman encoding -var txtEncoder = charmap.Macintosh.NewEncoder() - // Assign functions to handle specific Hotline transaction types func RegisterHandlers(srv *hotline.Server) { srv.HandleFunc(hotline.TranAgreed, HandleTranAgreed) @@ -303,7 +296,7 @@ func HandleGetFileInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho fileName := t.GetField(hotline.FieldFileName).Data filePath := t.GetField(hotline.FieldFilePath).Data - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } @@ -313,7 +306,7 @@ func HandleGetFileInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho return res } - encodedName, err := txtEncoder.String(fw.Name) + encodedName, err := cc.TextEncoder().String(fw.Name) if err != nil { return res } @@ -355,7 +348,7 @@ func HandleSetFileInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho fileName := t.GetField(hotline.FieldFileName).Data filePath := t.GetField(hotline.FieldFilePath).Data - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } @@ -394,7 +387,7 @@ func HandleSetFileInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho } } - fullNewFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, t.GetField(hotline.FieldFileNewName).Data) + fullNewFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, t.GetField(hotline.FieldFileNewName).Data, cc.TextDecoder()) if err != nil { return nil } @@ -416,11 +409,11 @@ func HandleSetFileInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho if !cc.Authorize(hotline.AccessRenameFile) { return cc.NewErrReply(t, ErrMsgNotAllowedRenameFiles) } - fileDir, err := hotline.ReadPath(cc.FileRoot(), filePath, []byte{}) + fileDir, err := hotline.ReadPath(cc.FileRoot(), filePath, []byte{}, cc.TextDecoder()) if err != nil { return nil } - hlFile.Name, err = txtDecoder.String(string(fileNewName)) + hlFile.Name, err = cc.TextDecoder().String(string(fileNewName)) if err != nil { return res } @@ -452,7 +445,7 @@ func HandleDeleteFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot fileName := t.GetField(hotline.FieldFileName).Data filePath := t.GetField(hotline.FieldFilePath).Data - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } @@ -497,12 +490,12 @@ func HandleDeleteFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot func HandleMoveFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { fileName := string(t.GetField(hotline.FieldFileName).Data) - filePath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, t.GetField(hotline.FieldFileName).Data) + filePath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, t.GetField(hotline.FieldFileName).Data, cc.TextDecoder()) if err != nil { return res } - fileNewPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFileNewPath).Data, nil) + fileNewPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFileNewPath).Data, nil, cc.TextDecoder()) if err != nil { return res } @@ -571,11 +564,11 @@ func HandleNewFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotl // 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) + subPath, err := cc.TextDecoder().String(subPath) if err != nil { return res } - folderName, err = txtDecoder.String(folderName) + folderName, err = cc.TextDecoder().String(folderName) if err != nil { return res } @@ -1594,7 +1587,7 @@ func HandleDownloadFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []h dataOffset = int64(binary.BigEndian.Uint32(frd.ForkInfoList[0].DataSize[:])) } - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } @@ -1658,7 +1651,7 @@ func HandleDownloadFolder(cc *hotline.ClientConn, t *hotline.Transaction) (res [ return cc.NewErrReply(t, ErrMsgNotAllowedDownloadFolders) } - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, t.GetField(hotline.FieldFileName).Data) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, t.GetField(hotline.FieldFileName).Data, cc.TextDecoder()) if err != nil { return nil } @@ -1769,7 +1762,7 @@ func HandleUploadFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot return cc.NewErrReply(t, fmt.Sprintf(ErrMsgUploadRestrictedTemplate, "file", string(fileName))) } } - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } @@ -1911,15 +1904,18 @@ func HandleGetFileNameList(cc *hotline.ClientConn, t *hotline.Transaction) (res cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil, + cc.TextDecoder(), ) if err != nil { - return res + cc.Logger.Error("error reading file path", "err", err) + return cc.NewErrReply(t, "Cannot get file list.") } var fp hotline.FilePath if t.GetField(hotline.FieldFilePath).Data != nil { if _, err = fp.Write(t.GetField(hotline.FieldFilePath).Data); err != nil { - return res + cc.Logger.Error("error parsing file path", "err", err) + return cc.NewErrReply(t, "Cannot get file list.") } } @@ -1928,9 +1924,10 @@ func HandleGetFileNameList(cc *hotline.ClientConn, t *hotline.Transaction) (res return cc.NewErrReply(t, ErrMsgNotAllowedViewDropBoxes) } - fileNames, err := hotline.GetFileNameList(fullPath, cc.Server.Config.IgnoreFiles) + fileNames, err := hotline.GetFileNameList(fullPath, cc.Server.Config.IgnoreFiles, cc.TextEncoder(), cc.Logger) if err != nil { - return res + cc.Logger.Error("error getting file name list", "err", err) + return cc.NewErrReply(t, "Cannot get file list.") } res = append(res, cc.NewReply(t, fileNames...)) @@ -2186,12 +2183,12 @@ func HandleMakeAlias(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotl filePath := t.GetField(hotline.FieldFilePath).Data fileNewPath := t.GetField(hotline.FieldFileNewPath).Data - fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName) + fullFilePath, err := hotline.ReadPath(cc.FileRoot(), filePath, fileName, cc.TextDecoder()) if err != nil { return res } - fullNewFilePath, err := hotline.ReadPath(cc.FileRoot(), fileNewPath, fileName) + fullNewFilePath, err := hotline.ReadPath(cc.FileRoot(), fileNewPath, fileName, cc.TextDecoder()) if err != nil { return res } diff --git a/internal/mobius/transaction_handlers_test.go b/internal/mobius/transaction_handlers_test.go index 9e995b5..e9dfdfc 100644 --- a/internal/mobius/transaction_handlers_test.go +++ b/internal/mobius/transaction_handlers_test.go @@ -17,6 +17,7 @@ import ( "github.com/jhalter/mobius/hotline" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "golang.org/x/text/encoding/charmap" ) type mockReadWriteSeeker struct { @@ -113,6 +114,8 @@ func TestHandleSetChatSubject(t *testing.T) { cc: &hotline.ClientConn{ UserName: []byte{0x00, 0x01}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} m.On("Members", hotline.ChatID{0x0, 0x0, 0x0, 0x1}).Return([]*hotline.ClientConn{ @@ -227,6 +230,8 @@ func TestHandleLeaveChat(t *testing.T) { cc: &hotline.ClientConn{ ID: [2]byte{0, 2}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} m.On("Members", hotline.ChatID{0x0, 0x0, 0x0, 0x1}).Return([]*hotline.ClientConn{ @@ -302,6 +307,8 @@ func TestHandleGetUserNameList(t *testing.T) { cc: &hotline.ClientConn{ ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -374,6 +381,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte{0x00, 0x01}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -435,6 +444,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte{0x00, 0x01}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -491,6 +502,8 @@ func TestHandleChatSend(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -522,6 +535,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte("Testy McTest"), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -584,6 +599,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte("Testy McTest"), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -642,6 +659,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte{0x00, 0x01}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -694,6 +713,8 @@ func TestHandleChatSend(t *testing.T) { }, UserName: []byte{0x00, 0x01}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} m.On("Members", hotline.ChatID{0x0, 0x0, 0x0, 0x1}).Return([]*hotline.ClientConn{ @@ -786,6 +807,8 @@ func TestHandleGetFileInfo(t *testing.T) { ID: [2]byte{0, 1}, Account: &hotline.Account{}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), FS: &hotline.OSFileStore{}, Config: hotline.Config{ FileRoot: func() string { @@ -884,6 +907,8 @@ func TestHandleNewFolder(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: "/Files/", }, @@ -926,6 +951,8 @@ func TestHandleNewFolder(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: "/Files", }, @@ -962,6 +989,8 @@ func TestHandleNewFolder(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: "/Files/", }, @@ -996,6 +1025,8 @@ func TestHandleNewFolder(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: "/Files/", }, @@ -1032,6 +1063,8 @@ func TestHandleNewFolder(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: "/Files/", }, @@ -1091,6 +1124,8 @@ func TestHandleUploadFile(t *testing.T) { args: args{ cc: &hotline.ClientConn{ Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), FS: &hotline.OSFileStore{}, FileTransferMgr: hotline.NewMemFileTransferMgr(), Config: hotline.Config{ @@ -1190,6 +1225,8 @@ func TestHandleMakeAlias(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { path, _ := os.Getwd() @@ -1236,6 +1273,8 @@ func TestHandleMakeAlias(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { path, _ := os.Getwd() @@ -1281,6 +1320,8 @@ func TestHandleMakeAlias(t *testing.T) { Access: hotline.AccessBitmap{}, }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { path, _ := os.Getwd() @@ -1347,6 +1388,8 @@ func TestHandleGetUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "guest").Return(&hotline.Account{ @@ -1387,6 +1430,8 @@ func TestHandleGetUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -1417,6 +1462,8 @@ func TestHandleGetUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "nonExistentUser").Return((*hotline.Account)(nil)) @@ -1472,6 +1519,8 @@ func TestHandleDeleteUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Delete", "testuser").Return(nil) @@ -1506,6 +1555,8 @@ func TestHandleDeleteUser(t *testing.T) { Access: hotline.AccessBitmap{}, }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -1555,6 +1606,8 @@ func TestHandleGetMsgs(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), MessageBoard: func() *mockReadWriteSeeker { m := mockReadWriteSeeker{} m.On("Seek", int64(0), 0).Return(int64(0), nil) @@ -1592,6 +1645,8 @@ func TestHandleGetMsgs(t *testing.T) { }, Logger: NewTestLogger(), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), MessageBoard: func() *mockReadWriteSeeker { m := mockReadWriteSeeker{} m.On("Seek", int64(0), 0).Return(int64(0), nil) @@ -1622,6 +1677,8 @@ func TestHandleGetMsgs(t *testing.T) { Access: hotline.AccessBitmap{}, }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -1669,6 +1726,8 @@ func TestHandleNewUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -1698,6 +1757,8 @@ func TestHandleNewUser(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "userB").Return((*hotline.Account)(nil)) @@ -1758,6 +1819,8 @@ func TestHandleListUsers(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -1787,6 +1850,8 @@ func TestHandleListUsers(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("List").Return([]hotline.Account{ @@ -1849,7 +1914,7 @@ func TestHandleDownloadFile(t *testing.T) { return bits }(), }, - Server: &hotline.Server{}, + Server: &hotline.Server{TextDecoder: charmap.Macintosh.NewDecoder(), TextEncoder: charmap.Macintosh.NewEncoder()}, }, t: hotline.NewTransaction(hotline.TranDownloadFile, [2]byte{0, 1}), }, @@ -1876,6 +1941,8 @@ func TestHandleDownloadFile(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), FS: &hotline.OSFileStore{}, FileTransferMgr: hotline.NewMemFileTransferMgr(), Config: hotline.Config{ @@ -1915,6 +1982,8 @@ func TestHandleDownloadFile(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), FS: &hotline.OSFileStore{}, // FS: func() *hotline.MockFileStore { @@ -2005,6 +2074,8 @@ func TestHandleUpdateUser(t *testing.T) { cc: &hotline.ClientConn{ Logger: NewTestLogger(), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "bbb").Return((*hotline.Account)(nil)) @@ -2056,6 +2127,8 @@ func TestHandleUpdateUser(t *testing.T) { cc: &hotline.ClientConn{ Logger: NewTestLogger(), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Logger: NewTestLogger(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} @@ -2109,7 +2182,7 @@ func TestHandleUpdateUser(t *testing.T) { args: args{ cc: &hotline.ClientConn{ Logger: NewTestLogger(), - Server: &hotline.Server{}, + Server: &hotline.Server{TextDecoder: charmap.Macintosh.NewDecoder(), TextEncoder: charmap.Macintosh.NewEncoder()}, Account: &hotline.Account{ Access: hotline.AccessBitmap{}, }, @@ -2194,6 +2267,8 @@ func TestHandleDelNewsArt(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("DeleteArticle", []string{"test"}, uint32(1), false).Return(errors.New("write error")) @@ -2275,6 +2350,8 @@ func TestHandleDisconnectUser(t *testing.T) { args: args{ cc: &hotline.ClientConn{ Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x1}).Return(&hotline.ClientConn{ @@ -2374,6 +2451,8 @@ func TestHandleSendInstantMsg(t *testing.T) { ID: [2]byte{0, 1}, UserName: []byte("User1"), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x2}).Return(&hotline.ClientConn{ @@ -2422,6 +2501,8 @@ func TestHandleSendInstantMsg(t *testing.T) { ID: [2]byte{0, 1}, UserName: []byte("User1"), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x2}).Return(&hotline.ClientConn{ @@ -2479,6 +2560,8 @@ func TestHandleSendInstantMsg(t *testing.T) { ID: [2]byte{0, 1}, UserName: []byte("User1"), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x2}).Return(&hotline.ClientConn{ @@ -2544,6 +2627,8 @@ func TestHandleDeleteFile(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { return "/fakeRoot/Files" @@ -2600,6 +2685,8 @@ func TestHandleDeleteFile(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { return "/fakeRoot/Files" @@ -2678,6 +2765,8 @@ func TestHandleGetFileNameList(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ FileRoot: func() string { @@ -2717,7 +2806,7 @@ func TestHandleGetFileNameList(t *testing.T) { return filepath.Join(path, "/test/config/Files/getFileNameListTestDir") }(), }, - Server: &hotline.Server{}, + Server: &hotline.Server{TextDecoder: charmap.Macintosh.NewDecoder(), TextEncoder: charmap.Macintosh.NewEncoder()}, }, t: hotline.NewTransaction( hotline.TranGetFileNameList, [2]byte{0, 1}, @@ -2783,6 +2872,8 @@ func TestHandleGetClientInfoText(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -2817,6 +2908,8 @@ func TestHandleGetClientInfoText(t *testing.T) { Login: "test", }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x1}).Return(&hotline.ClientConn{ @@ -2916,6 +3009,8 @@ func TestHandleTranAgreed(t *testing.T) { ID: [2]byte{0, 1}, Logger: NewTestLogger(), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ BannerFile: "Banner.jpg", }, @@ -2971,6 +3066,8 @@ func TestHandleTranAgreed(t *testing.T) { ID: [2]byte{0, 1}, Logger: NewTestLogger(), Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ BannerFile: "Banner.gif", }, @@ -3036,6 +3133,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { UserName: []byte("Guest"), Flags: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3080,6 +3179,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { UserName: []byte("Guest"), Flags: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3122,6 +3223,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { UserName: []byte("Guest"), Flags: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3164,6 +3267,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { UserName: []byte("Guest"), Flags: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3207,6 +3312,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { Flags: [2]byte{0, 1}, Version: []byte{0x01, 0x03}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3251,6 +3358,8 @@ func TestHandleSetClientUserInfo(t *testing.T) { Flags: [2]byte{0, 1}, Version: []byte{0x01, 0x03}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("List").Return([]*hotline.ClientConn{ @@ -3307,6 +3416,8 @@ func TestHandleDelNewsItem(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("NewsItem", []string{"test"}).Return(hotline.NewsCategoryListData15{ @@ -3348,6 +3459,8 @@ func TestHandleDelNewsItem(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("NewsItem", []string{"test"}).Return(hotline.NewsCategoryListData15{ @@ -3393,6 +3506,8 @@ func TestHandleDelNewsItem(t *testing.T) { }, ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("NewsItem", []string{"test"}).Return(hotline.NewsCategoryListData15{Type: hotline.NewsBundle}) @@ -3476,6 +3591,8 @@ func TestHandleTranOldPostNews(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), Config: hotline.Config{ NewsDateFormat: "", }, @@ -3566,6 +3683,8 @@ func TestHandleInviteNewChat(t *testing.T) { Icon: []byte{0, 1}, Flags: [2]byte{0, 0}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0x0, 0x2}).Return(&hotline.ClientConn{ @@ -3625,6 +3744,8 @@ func TestHandleInviteNewChat(t *testing.T) { Icon: []byte{0, 1}, Flags: [2]byte{0, 0}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ClientMgr: func() *hotline.MockClientMgr { m := hotline.MockClientMgr{} m.On("Get", hotline.ClientID{0, 2}).Return(&hotline.ClientConn{ @@ -3722,6 +3843,8 @@ func TestHandleGetNewsArtData(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("GetArticle", []string{"Example Category"}, uint32(1)).Return(&hotline.NewsArtData{ @@ -3795,6 +3918,8 @@ func TestHandleGetNewsArtNameList(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -3826,6 +3951,8 @@ func TestHandleGetNewsArtNameList(t *testing.T) { // }(), // }, // Server: &hotline.Server{ + // TextDecoder: charmap.Macintosh.NewDecoder(), + // TextEncoder: charmap.Macintosh.NewEncoder(), // ThreadedNewsMgr: func() *mockThreadNewsMgr { // m := mockThreadNewsMgr{} // m.On("ListArticles", []string{"Example Category"}).Return(NewsArtListData{ @@ -3893,6 +4020,8 @@ func TestHandleNewNewsFldr(t *testing.T) { }(), }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), //Accounts: map[string]*Account{}, }, }, @@ -3926,6 +4055,8 @@ func TestHandleNewNewsFldr(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("CreateGrouping", []string{"test"}, "testFolder", hotline.NewsBundle).Return(nil) @@ -3968,6 +4099,8 @@ func TestHandleNewNewsFldr(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("CreateGrouping", []string{"test"}, "testFolder", hotline.NewsBundle).Return(errors.New("write error")) @@ -4071,6 +4204,8 @@ func TestHandlePostNewsArt(t *testing.T) { args: args{ cc: &hotline.ClientConn{ Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("PostArticle", []string{"www"}, uint32(0), mock.AnythingOfType("hotline.NewsArtData")).Return(nil) @@ -4105,6 +4240,8 @@ func TestHandlePostNewsArt(t *testing.T) { args: args{ cc: &hotline.ClientConn{ Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("PostArticle", []string{"www"}, uint32(0), mock.AnythingOfType("hotline.NewsArtData")).Return(errors.New("write error")) @@ -4261,6 +4398,8 @@ func TestHandleJoinChat(t *testing.T) { Access: hotline.AccessBitmap{255, 255, 255, 255, 255, 255, 255, 255}, }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} // Mock existing members before join @@ -4377,6 +4516,8 @@ func TestHandleJoinChat(t *testing.T) { Access: hotline.AccessBitmap{255, 255, 255, 255, 255, 255, 255, 255}, }, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} // Mock empty chat before join @@ -4446,6 +4587,8 @@ func TestHandleRejectChatInvite(t *testing.T) { UserName: []byte("RejectUser"), ID: [2]byte{0, 3}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} // Mock current members of the chat @@ -4497,6 +4640,8 @@ func TestHandleRejectChatInvite(t *testing.T) { UserName: []byte("LoneRejecter"), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} // Mock empty chat (no members) @@ -4524,6 +4669,8 @@ func TestHandleRejectChatInvite(t *testing.T) { UserName: []byte("Shy"), ID: [2]byte{0, 2}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ChatMgr: func() *hotline.MockChatManager { m := hotline.MockChatManager{} // Mock chat with single member @@ -4738,7 +4885,7 @@ func TestHandleSetUser(t *testing.T) { return bits }(), }, - Server: &hotline.Server{}, + Server: &hotline.Server{TextDecoder: charmap.Macintosh.NewDecoder(), TextEncoder: charmap.Macintosh.NewEncoder()}, }, t: hotline.NewTransaction( hotline.TranSetUser, [2]byte{0, 1}, @@ -4770,6 +4917,8 @@ func TestHandleSetUser(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "testuser").Return((*hotline.Account)(nil)) @@ -4809,6 +4958,8 @@ func TestHandleSetUser(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), AccountManager: func() *MockAccountManager { m := MockAccountManager{} m.On("Get", "testuser").Return(&hotline.Account{ @@ -4868,7 +5019,7 @@ func TestHandleNewNewsCat(t *testing.T) { return bits }(), }, - Server: &hotline.Server{}, + Server: &hotline.Server{TextDecoder: charmap.Macintosh.NewDecoder(), TextEncoder: charmap.Macintosh.NewEncoder()}, }, t: hotline.NewTransaction( hotline.TranNewNewsCat, [2]byte{0, 1}, @@ -4900,6 +5051,8 @@ func TestHandleNewNewsCat(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("CreateGrouping", []string{"test"}, "TestCat", hotline.NewsCategory).Return(errors.New("write error")) @@ -4945,6 +5098,8 @@ func TestHandleNewNewsCat(t *testing.T) { Logger: NewTestLogger(), ID: [2]byte{0, 1}, Server: &hotline.Server{ + TextDecoder: charmap.Macintosh.NewDecoder(), + TextEncoder: charmap.Macintosh.NewEncoder(), ThreadedNewsMgr: func() *hotline.MockThreadNewsMgr { m := hotline.MockThreadNewsMgr{} m.On("CreateGrouping", []string{"test"}, "TestCat", hotline.NewsCategory).Return(nil) -- cgit