]> git.r.bdr.sh - rbdr/mobius/commitdiff
Allow for personal ~ folder
authorRuben Beltran del Rio <redacted>
Mon, 3 Feb 2025 22:09:40 +0000 (23:09 +0100)
committerRuben Beltran del Rio <redacted>
Mon, 3 Feb 2025 22:09:40 +0000 (23:09 +0100)
README.md
internal/mobius/friendship_quest_file_extensions.go [new file with mode: 0644]
internal/mobius/transaction_handlers.go

index 2c9772b5432e6d754d4244318963d9c306b6de10..c76ad4fc7c3b8613e826e2e4b483ba8454fca6b2 100644 (file)
--- a/README.md
+++ b/README.md
@@ -5,6 +5,12 @@
 </picture>
 -->
 
+# Mobius (Friendship Quest Remix)
+
+A fork of [mobius](https://github.com/jhalter/mobius) with some extra features:
+
+1. If you have upload permission, you get your own `~` folder.
+
 # Mobius
 
 Mobius is a cross-platform command line [Hotline](https://en.wikipedia.org/wiki/Hotline_Communications) server implemented in Golang.
diff --git a/internal/mobius/friendship_quest_file_extensions.go b/internal/mobius/friendship_quest_file_extensions.go
new file mode 100644 (file)
index 0000000..fb23b79
--- /dev/null
@@ -0,0 +1,158 @@
+package mobius
+
+import (
+       "bytes"
+       "encoding/binary"
+       "fmt"
+       "os"
+       "path/filepath"
+       "strings"
+
+       "github.com/jhalter/mobius/hotline"
+)
+
+// 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")
+               }
+       }
+       return requestedPath, nil
+}
+
+// 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
+        }
+    }
+}
+
+// Encode 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
+}
+
+// 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
+       }
+
+       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...))
+               }
+               return append(res, cc.NewReply(t, fileNames...))
+       }
+
+       if strings.HasPrefix(requestedPath, filepath.Join(cc.FileRoot(), "~")) {
+               resolvedPath, err := ResolveUserPath(cc, requestedPath[len(cc.FileRoot())+1:])
+               if err != nil {
+                       return res
+               }
+               updateTransactionPath(t, resolvedPath)
+               return HandleGetFileNameList(cc, t)
+       }
+
+       return HandleGetFileNameList(cc, t)
+}
+
+// HandleUploadFileWithUserFolders ensures uploads go to the correct user folder when using `~`.
+func HandleUploadFileWithUserFolders(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.")
+       }
+
+       updateTransactionPath(t, resolvedPath)
+       return HandleUploadFile(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.")
+       }
+
+       updateTransactionPath(t, resolvedPath)
+       return HandleUploadFolder(cc, t)
+}
index cf2357c4e21f5585e85c8f568ef719c7513bdec1..0cba65afc204d82d8c9529a6d26325d0211f2c42 100644 (file)
@@ -35,7 +35,7 @@ func RegisterHandlers(srv *hotline.Server) {
        srv.HandleFunc(hotline.TranDownloadFldr, HandleDownloadFolder)
        srv.HandleFunc(hotline.TranGetClientInfoText, HandleGetClientInfoText)
        srv.HandleFunc(hotline.TranGetFileInfo, HandleGetFileInfo)
-       srv.HandleFunc(hotline.TranGetFileNameList, HandleGetFileNameList)
+       srv.HandleFunc(hotline.TranGetFileNameList, HandleGetFileNameListWithUserFolders)
        srv.HandleFunc(hotline.TranGetMsgs, HandleGetMsgs)
        srv.HandleFunc(hotline.TranGetNewsArtData, HandleGetNewsArtData)
        srv.HandleFunc(hotline.TranGetNewsArtNameList, HandleGetNewsArtNameList)
@@ -63,8 +63,8 @@ func RegisterHandlers(srv *hotline.Server) {
        srv.HandleFunc(hotline.TranSetClientUserInfo, HandleSetClientUserInfo)
        srv.HandleFunc(hotline.TranSetFileInfo, HandleSetFileInfo)
        srv.HandleFunc(hotline.TranSetUser, HandleSetUser)
-       srv.HandleFunc(hotline.TranUploadFile, HandleUploadFile)
-       srv.HandleFunc(hotline.TranUploadFldr, HandleUploadFolder)
+       srv.HandleFunc(hotline.TranUploadFile, HandleUploadFileWithUserFolders)
+       srv.HandleFunc(hotline.TranUploadFldr, HandleUploadFolderWithUserFolders)
        srv.HandleFunc(hotline.TranUserBroadcast, HandleUserBroadcast)
        srv.HandleFunc(hotline.TranDownloadBanner, HandleDownloadBanner)
 }