)
// ResolveUserPath processes paths for the special `~` directory.
-// If the requested path starts with `~`, it returns the user's corresponding folder.
func ResolveUserPath(cc *hotline.ClientConn, requestedPath string) (string, error) {
- if strings.HasPrefix(requestedPath, "~") {
- userFolder := filepath.Join("~", cc.Account.Login)
- actualUserFolder := filepath.Join(cc.FileRoot(), userFolder)
- if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() {
- return strings.Replace(requestedPath, "~", userFolder, 1), nil
- } else {
- return "", fmt.Errorf("user folder does not exist")
- }
+ if !strings.HasPrefix(requestedPath, "~") {
+ return requestedPath, nil
+ }
+
+ userFolder := filepath.Join("~", cc.Account.Login)
+ actualUserFolder := filepath.Join(cc.FileRoot(), userFolder)
+ if stat, err := os.Stat(actualUserFolder); err == nil && stat.IsDir() {
+ return strings.Replace(requestedPath, "~", userFolder, 1), nil
}
- return requestedPath, nil
+
+ return "", fmt.Errorf("user folder does not exist")
}
// updateTransactionPath updates the FieldFilePath in a transaction in-place.
func updateTransactionPath(t *hotline.Transaction, newPath string) {
- for i, field := range t.Fields {
- if field.Type == hotline.FieldFilePath {
-
- // Convert newPath to the correct binary format
- encodedPath, err := txtEncoder.String(newPath)
- if err != nil {
- return // Encoding failure, don't update
- }
-
- // Convert the new path into the correct binary format for FilePath
- fpBytes, err := encodeFilePath(encodedPath)
- if err != nil {
- return
- }
-
- // Assign correctly formatted binary path
- t.Fields[i].Data = fpBytes
- return
- }
- }
+ for i, field := range t.Fields {
+ if field.Type == hotline.FieldFilePath {
+ if encodedPath, err := txtEncoder.String(newPath); err == nil {
+ if fpBytes, err := encodeFilePath(encodedPath); err == nil {
+ t.Fields[i].Data = fpBytes
+ }
+ }
+ return
+ }
+ }
}
-// Encode a string path into hotline.FilePath binary format
+// encodeFilePath encodes a string path into hotline.FilePath binary format.
func encodeFilePath(path string) ([]byte, error) {
- components := strings.Split(path, string(filepath.Separator))
- var fp hotline.FilePath
-
- // Set the item count
- binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
-
- var buffer bytes.Buffer
- err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount)
- if err != nil {
- return nil, err
- }
-
- for _, component := range components {
- if component == "" {
- continue
- }
-
- if len(component) > 255 { // Ensure component size is within range
- return nil, fmt.Errorf("file path component too long")
- }
-
- // Convert to MacRoman encoding
- encodedComponent, err := txtEncoder.String(component)
- if err != nil {
- return nil, err
- }
-
- // Write length and name
- buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))}) // Leading bytes
- buffer.Write([]byte(encodedComponent))
- }
-
- return buffer.Bytes(), nil
-}
+ components := strings.Split(path, string(filepath.Separator))
+ var fp hotline.FilePath
+ binary.BigEndian.PutUint16(fp.ItemCount[:], uint16(len(components)))
-// HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
-func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
- requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
- if err != nil {
- return res
+ var buffer bytes.Buffer
+ if err := binary.Write(&buffer, binary.BigEndian, fp.ItemCount); err != nil {
+ return nil, err
}
- if requestedPath == cc.FileRoot() {
- fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
- if err != nil {
- return res
- }
-
- userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
- if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
- filteredFiles := []hotline.Field{}
- for _, file := range fileNames {
- if !strings.Contains(string(file.Data), "~") {
- filteredFiles = append(filteredFiles, file)
- }
- }
- return append(res, cc.NewReply(t, filteredFiles...))
+ for _, component := range components {
+ if component == "" || len(component) > 255 {
+ return nil, fmt.Errorf("invalid file path component")
}
- return append(res, cc.NewReply(t, fileNames...))
- }
-
- if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
- resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
+ encodedComponent, err := txtEncoder.String(component)
if err != nil {
- return res
+ return nil, err
}
- updateTransactionPath(t, resolvedPath)
- return HandleGetFileNameList(cc, t)
+ buffer.Write([]byte{0x00, 0x00, byte(len(encodedComponent))})
+ buffer.Write([]byte(encodedComponent))
}
- return HandleGetFileNameList(cc, t)
+ return buffer.Bytes(), nil
}
-// HandleUploadFileWithUserFolders ensures uploads go to the correct user folder when using `~`.
-func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
+// handleFileTransaction handles various file operations that require resolving `~`
+func handleFileTransaction(cc *hotline.ClientConn, t *hotline.Transaction, handler func(*hotline.ClientConn, *hotline.Transaction) []hotline.Transaction, errMsg string) []hotline.Transaction {
requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
if err != nil {
- return res
+ return nil
}
resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
if err != nil {
- return cc.NewErrReply(t, "Cannot upload to non-existent user folder.")
+ return cc.NewErrReply(t, errMsg)
}
updateTransactionPath(t, resolvedPath)
- return HandleUploadFile(cc, t)
+ return handler(cc, t)
}
-// HandleUploadFolderWithUserFolders ensures directory uploads go to the correct user folder when using `~`.
-func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
- requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
- if err != nil {
- return res
- }
-
- resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
- if err != nil {
- return cc.NewErrReply(t, "Cannot upload to non-existent user folder.")
- }
+// File operation handlers
+func HandleUploadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
+ return handleFileTransaction(cc, t, HandleUploadFile, "Cannot upload to non-existent user folder.")
+}
- updateTransactionPath(t, resolvedPath)
- return HandleUploadFolder(cc, t)
+func HandleUploadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
+ return handleFileTransaction(cc, t, HandleUploadFolder, "Cannot upload to non-existent user folder.")
}
-func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
- requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
- if err != nil {
- return res
- }
+func HandleDeleteFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
+ return handleFileTransaction(cc, t, HandleDeleteFile, "Cannot delete non-existent file.")
+}
- resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
- if err != nil {
- return cc.NewErrReply(t, "Cannot delete non-existent file.")
- }
+func HandleDownloadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
+ return handleFileTransaction(cc, t, HandleDownloadFile, "Cannot download non-existent user file.")
+}
- updateTransactionPath(t, resolvedPath)
- return HandleDeleteFile(cc, t)
+func HandleDownloadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
+ return handleFileTransaction(cc, t, HandleDownloadFolder, "Cannot download non-existent user folder.")
}
-func HandleDownloadFileWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
+// HandleGetFileNameListWithUserFolders modifies the file listing behavior for `~`
+func HandleGetFileNameListWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) []hotline.Transaction {
requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
if err != nil {
- return res
+ return nil
}
- resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
- if err != nil {
- return cc.NewErrReply(t, "Cannot download non-existent user file.")
- }
-
- updateTransactionPath(t, resolvedPath)
- return HandleDownloadFile(cc, t)
-}
+ if requestedPath == cc.FileRoot() {
+ fileNames, err := hotline.GetFileNameList(cc.FileRoot(), cc.Server.Config.IgnoreFiles)
+ if err != nil {
+ return nil
+ }
-func HandleDownloadFolderWithUserFolders(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) {
- requestedPath, err := hotline.ReadPath(cc.FileRoot(), t.GetField(hotline.FieldFilePath).Data, nil)
- if err != nil {
- return res
+ userFolder := filepath.Join(cc.FileRoot(), "~", cc.Account.Login)
+ if stat, err := os.Stat(userFolder); err != nil || !stat.IsDir() {
+ filteredFiles := make([]hotline.Field, 0, len(fileNames))
+ for _, file := range fileNames {
+ if !strings.Contains(string(file.Data), "~") {
+ filteredFiles = append(filteredFiles, file)
+ }
+ }
+ return []hotline.Transaction{cc.NewReply(t, filteredFiles...)}
+ }
+ return []hotline.Transaction{cc.NewReply(t, fileNames...)}
}
- resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
- if err != nil {
- return cc.NewErrReply(t, "Cannot download non-existent user folder.")
+ if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
+ resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
+ if err != nil {
+ return nil
+ }
+ updateTransactionPath(t, resolvedPath)
+ return HandleGetFileNameList(cc, t)
}
- updateTransactionPath(t, resolvedPath)
- return HandleDownloadFolder(cc, t)
+ return HandleGetFileNameList(cc, t)
}