From: Ruben Beltran del Rio Date: Mon, 3 Feb 2025 22:09:40 +0000 (+0100) Subject: Allow for personal ~ folder X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/7bd6f5adda126bbe7caf94330e08d9adf0e66466 Allow for personal ~ folder --- diff --git a/README.md b/README.md index 2c9772b..c76ad4f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,12 @@ --> +# 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 index 0000000..fb23b79 --- /dev/null +++ b/internal/mobius/friendship_quest_file_extensions.go @@ -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) +} diff --git a/internal/mobius/transaction_handlers.go b/internal/mobius/transaction_handlers.go index cf2357c..0cba65a 100644 --- a/internal/mobius/transaction_handlers.go +++ b/internal/mobius/transaction_handlers.go @@ -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) }