]> git.r.bdr.sh - rbdr/mobius/blob - internal/mobius/friendship_quest_file_extensions.go
1ef8ad6a4d26a55bf42f802a6a5e40ba192c060f
[rbdr/mobius] / internal / mobius / friendship_quest_file_extensions.go
1 package mobius
2
3 import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "github.com/jhalter/mobius/hotline"
12 )
13
14 // ResolveUserPath processes paths for the special `~` directory.
15 // If the requested path starts with `~`, it returns the user's corresponding folder.
16 func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) {
17 if strings.HasPrefix(requestedPath, "~") {
18 userFolder := filepath.Join("~", cc.Account.Login)
19 actualUserFolder := filepath.Join(cc.FileRoot(), userFolder)
20 if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() {
21 return strings.Replace(requestedPath, "~", userFolder, 1), nil
22 } else {
23 return "", fmt.Errorf("user folder does not exist")
24 }
25 }
26 return requestedPath, nil
27 }
28
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
34 // Convert newPath to the correct binary format
35 encodedPath, err := txtEncoder.String(newPath)
36 if err != nil {
37 return // Encoding failure, don't update
38 }
39
40 // Convert the new path into the correct binary format for FilePath
41 fpBytes, err := encodeFilePath(encodedPath)
42 if err != nil {
43 return
44 }
45
46 // Assign correctly formatted binary path
47 t.Fields[i].Data = fpBytes
48 return
49 }
50 }
51 }
52
53 // Encode a string path into hotline.FilePath binary format
54 func encodeFilePath(path string) ([]byte, error) {
55 components := strings.Split(path, string(filepath.Separator))
56 var fp hotline.FilePath
57
58 // Set the item count
59 binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
60
61 var buffer bytes.Buffer
62 err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount)
63 if err != nil {
64 return nil, err
65 }
66
67 for _, component := range components {
68 if component == "" {
69 continue
70 }
71
72 if len(component) > 255 { // Ensure component size is within range
73 return nil, fmt.Errorf("file path component too long")
74 }
75
76 // Convert to MacRoman encoding
77 encodedComponent, err := txtEncoder.String(component)
78 if err != nil {
79 return nil, err
80 }
81
82 // Write length and name
83 buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))}) // Leading bytes
84 buffer.Write([]byte(encodedComponent))
85 }
86
87 return buffer.Bytes(), nil
88 }
89
90 // HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
91 func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
92 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
93 if err != nil {
94 return res
95 }
96
97 if requestedPath == cc.FileRoot() {
98 fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
99 if err != nil {
100 return res
101 }
102
103 userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
104 if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
105 filteredFiles := []hotline.Field{}
106 for _, file := range fileNames {
107 if !strings.Contains(string(file.Data), "~") {
108 filteredFiles = append(filteredFiles, file)
109 }
110 }
111 return append(res, cc.NewReply(t, filteredFiles...))
112 }
113 return append(res, cc.NewReply(t, fileNames...))
114 }
115
116 if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
117 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
118 if err != nil {
119 return res
120 }
121 updateTransactionPath(t, resolvedPath)
122 return HandleGetFileNameList(cc, t)
123 }
124
125 return HandleGetFileNameList(cc, t)
126 }
127
128 // HandleUploadFileWithUserFolders ensures uploads go to the correct user folder when using `~`.
129 func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
130 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
131 if err != nil {
132 return res
133 }
134
135 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
136 if err != nil {
137 return cc.NewErrReply(t, "Cannot upload to non-existent user folder.")
138 }
139
140 updateTransactionPath(t, resolvedPath)
141 return HandleUploadFile(cc, t)
142 }
143
144 // HandleUploadFolderWithUserFolders ensures directory uploads go to the correct user folder when using `~`.
145 func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
146 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
147 if err != nil {
148 return res
149 }
150
151 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
152 if err != nil {
153 return cc.NewErrReply(t, "Cannot upload to non-existent user folder.")
154 }
155
156 updateTransactionPath(t, resolvedPath)
157 return HandleUploadFolder(cc, t)
158 }
159
160 func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
161 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
162 if err != nil {
163 return res
164 }
165
166 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
167 if err != nil {
168 return cc.NewErrReply(t, "Cannot upload to non-existent user folder.")
169 }
170
171 updateTransactionPath(t, resolvedPath)
172 return HandleUploadFolder(cc, t)
173 }