]> git.r.bdr.sh - rbdr/mobius/blame - internal/mobius/friendship_quest_file_extensions.go
Account for the root
[rbdr/mobius] / internal / mobius / friendship_quest_file_extensions.go
CommitLineData
7bd6f5ad
RBR
1package mobius
2
3import (
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.
7bd6f5ad 15func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) {
6d81feb1
RBR
16 if !strings.HasPrefix(requestedPath, "~") {
17 return requestedPath, nil
18 }
19
20 userFolder := filepath.Join("~", cc.Account.Login)
21 actualUserFolder := filepath.Join(cc.FileRoot(), userFolder)
22 if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() {
23 return strings.Replace(requestedPath, "~", userFolder, 1), nil
7bd6f5ad 24 }
6d81feb1
RBR
25
26 return "", fmt.Errorf("user folder does not exist")
7bd6f5ad
RBR
27}
28
29// updateTransactionPath updates the FieldFilePath in a transaction in-place.
30func updateTransactionPath(t *hotline.Transaction, newPath string) {
6d81feb1
RBR
31 for i, field := range t.Fields {
32 if field.Type == hotline.FieldFilePath {
33 if encodedPath, err := txtEncoder.String(newPath); err == nil {
34 if fpBytes, err := encodeFilePath(encodedPath); err == nil {
35 t.Fields[i].Data = fpBytes
36 }
37 }
38 return
39 }
40 }
7bd6f5ad
RBR
41}
42
6d81feb1 43// encodeFilePath encodes a string path into hotline.FilePath binary format.
7bd6f5ad 44func encodeFilePath(path string) ([]byte, error) {
6d81feb1
RBR
45 components := strings.Split(path, string(filepath.Separator))
46 var fp hotline.FilePath
47 binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
7bd6f5ad 48
6d81feb1
RBR
49 var buffer bytes.Buffer
50 if err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount); err != nil {
51 return nil, err
7bd6f5ad
RBR
52 }
53
6d81feb1
RBR
54 for _, component := range components {
55 if component == "" || len(component) > 255 {
56 return nil, fmt.Errorf("invalid file path component")
7bd6f5ad 57 }
6d81feb1 58 encodedComponent, err := txtEncoder.String(component)
7bd6f5ad 59 if err != nil {
6d81feb1 60 return nil, err
7bd6f5ad 61 }
6d81feb1
RBR
62 buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))})
63 buffer.Write([]byte(encodedComponent))
7bd6f5ad
RBR
64 }
65
6d81feb1 66 return buffer.Bytes(), nil
7bd6f5ad
RBR
67}
68
6d81feb1
RBR
69// handleFileTransaction handles various file operations that require resolving `~`
70func handleFileTransaction(cc *hotline.ClientConn, t *hotline.Transaction, handler func(*hotline.ClientConn, *hotline.Transaction) []hotline.Transaction, errMsg string) []hotline.Transaction {
7bd6f5ad
RBR
71 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
72 if err != nil {
6d81feb1 73 return nil
7bd6f5ad
RBR
74 }
75
8f9edf2f
RBR
76 sliceLen := min(len(cc.FileRoot()) + 1, len(requestedPath))
77 resolvedPath, err := ResolveUserPath(cc, requestedPath[sliceLen:])
7bd6f5ad 78 if err != nil {
6d81feb1 79 return cc.NewErrReply(t, errMsg)
7bd6f5ad
RBR
80 }
81
82 updateTransactionPath(t, resolvedPath)
6d81feb1 83 return handler(cc, t)
7bd6f5ad
RBR
84}
85
6d81feb1
RBR
86// File operation handlers
87func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
88 return handleFileTransaction(cc, t, HandleUploadFile, "Cannot upload to non-existent user folder.")
89}
7bd6f5ad 90
6d81feb1
RBR
91func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
92 return handleFileTransaction(cc, t, HandleUploadFolder, "Cannot upload to non-existent user folder.")
7bd6f5ad 93}
8cd28eb8 94
6d81feb1
RBR
95func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
96 return handleFileTransaction(cc, t, HandleDeleteFile, "Cannot delete non-existent file.")
97}
8cd28eb8 98
6d81feb1
RBR
99func HandleDownloadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
100 return handleFileTransaction(cc, t, HandleDownloadFile, "Cannot download non-existent user file.")
101}
8cd28eb8 102
6d81feb1
RBR
103func HandleDownloadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
104 return handleFileTransaction(cc, t, HandleDownloadFolder, "Cannot download non-existent user folder.")
ba201a22
RBR
105}
106
6d81feb1
RBR
107// HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
108func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
ba201a22
RBR
109 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
110 if err != nil {
6d81feb1 111 return nil
ba201a22
RBR
112 }
113
6d81feb1
RBR
114 if requestedPath == cc.FileRoot() {
115 fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
116 if err != nil {
117 return nil
118 }
ba201a22 119
6d81feb1
RBR
120 userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
121 if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
122 filteredFiles := make([]hotline.Field, 0, len(fileNames))
123 for _, file := range fileNames {
124 if !strings.Contains(string(file.Data), "~") {
125 filteredFiles = append(filteredFiles, file)
126 }
127 }
128 return []hotline.Transaction{cc.NewReply(t, filteredFiles...)}
129 }
130 return []hotline.Transaction{cc.NewReply(t, fileNames...)}
ba201a22
RBR
131 }
132
6d81feb1
RBR
133 if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
134 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
135 if err != nil {
136 return nil
137 }
138 updateTransactionPath(t, resolvedPath)
139 return HandleGetFileNameList(cc, t)
ba201a22
RBR
140 }
141
6d81feb1 142 return HandleGetFileNameList(cc, t)
8cd28eb8 143}