11 "github.com/jhalter/mobius/hotline"
14 // ResolveUserPath processes paths for the special `~` directory.
15 func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) {
16 if !strings.HasPrefix(requestedPath, "~") {
17 return requestedPath, nil
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
26 return "", fmt.Errorf("user folder does not exist")
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 if encodedPath, err := txtEncoder.String(newPath); err == nil {
34 if fpBytes, err := encodeFilePath(encodedPath); err == nil {
35 t.Fields[i].Data = fpBytes
43 // encodeFilePath encodes a string path into hotline.FilePath binary format.
44 func encodeFilePath(path string) ([]byte, error) {
45 components := strings.Split(path, string(filepath.Separator))
46 var fp hotline.FilePath
47 binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
49 var buffer bytes.Buffer
50 if err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount); err != nil {
54 for _, component := range components {
55 if component == "" || len(component) > 255 {
56 return nil, fmt.Errorf("invalid file path component")
58 encodedComponent, err := txtEncoder.String(component)
62 buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))})
63 buffer.Write([]byte(encodedComponent))
66 return buffer.Bytes(), nil
69 // handleFileTransaction handles various file operations that require resolving `~`
70 func handleFileTransaction(cc *hotline.ClientConn, t *hotline.Transaction, handler func(*hotline.ClientConn, *hotline.Transaction) []hotline.Transaction, errMsg string) []hotline.Transaction {
71 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
76 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
78 return cc.NewErrReply(t, errMsg)
81 updateTransactionPath(t, resolvedPath)
85 // File operation handlers
86 func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
87 return handleFileTransaction(cc, t, HandleUploadFile, "Cannot upload to non-existent user folder.")
90 func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
91 return handleFileTransaction(cc, t, HandleUploadFolder, "Cannot upload to non-existent user folder.")
94 func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
95 return handleFileTransaction(cc, t, HandleDeleteFile, "Cannot delete non-existent file.")
98 func HandleDownloadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
99 return handleFileTransaction(cc, t, HandleDownloadFile, "Cannot download non-existent user file.")
102 func HandleDownloadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
103 return handleFileTransaction(cc, t, HandleDownloadFolder, "Cannot download non-existent user folder.")
106 // HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
107 func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
108 requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
113 if requestedPath == cc.FileRoot() {
114 fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
119 userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
120 if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
121 filteredFiles := make([]hotline.Field, 0, len(fileNames))
122 for _, file := range fileNames {
123 if !strings.Contains(string(file.Data), "~") {
124 filteredFiles = append(filteredFiles, file)
127 return []hotline.Transaction{cc.NewReply(t, filteredFiles...)}
129 return []hotline.Transaction{cc.NewReply(t, fileNames...)}
132 if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
133 resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
137 updateTransactionPath(t, resolvedPath)
138 return HandleGetFileNameList(cc, t)
141 return HandleGetFileNameList(cc, t)