]> git.r.bdr.sh - rbdr/mobius/commitdiff
Implement GetClientInfoText with per-client file transfer info
authorJeff Halter <redacted>
Thu, 23 Jun 2022 21:24:50 +0000 (14:24 -0700)
committerJeff Halter <redacted>
Thu, 23 Jun 2022 21:24:50 +0000 (14:24 -0700)
hotline/client_conn.go
hotline/file_transfer.go
hotline/file_wrapper.go
hotline/server.go
hotline/server_test.go
hotline/transaction_handlers.go
hotline/transaction_handlers_test.go
hotline/transfer.go
hotline/transfer_test.go

index 5780f6de04162a803b5affd305cc6de26e44457d..6a279776e7a38876164f931a727a322c1ba7c3b0 100644 (file)
@@ -2,11 +2,14 @@ package hotline
 
 import (
        "encoding/binary"
 
 import (
        "encoding/binary"
+       "fmt"
        "go.uber.org/zap"
        "golang.org/x/crypto/bcrypt"
        "io"
        "math/big"
        "sort"
        "go.uber.org/zap"
        "golang.org/x/crypto/bcrypt"
        "io"
        "math/big"
        "sort"
+       "strings"
+       "sync"
 )
 
 type byClientID []*ClientConn
 )
 
 type byClientID []*ClientConn
@@ -23,33 +26,6 @@ func (s byClientID) Less(i, j int) bool {
        return s[i].uint16ID() < s[j].uint16ID()
 }
 
        return s[i].uint16ID() < s[j].uint16ID()
 }
 
-const template = `Nickname:   %s
-Name:       %s
-Account:    %s
-Address:    %s
-
--------- File Downloads ---------
-
-%s
-
-------- Folder Downloads --------
-
-None.
-
---------- File Uploads ----------
-
-None.
-
--------- Folder Uploads ---------
-
-None.
-
-------- Waiting Downloads -------
-
-None.
-
-       `
-
 // ClientConn represents a client connected to a Server
 type ClientConn struct {
        Connection io.ReadWriteCloser
 // ClientConn represents a client connected to a Server
 type ClientConn struct {
        Connection io.ReadWriteCloser
@@ -64,9 +40,12 @@ type ClientConn struct {
        Version    *[]byte
        Idle       bool
        AutoReply  []byte
        Version    *[]byte
        Idle       bool
        AutoReply  []byte
-       Transfers  map[int][]*FileTransfer
-       Agreed     bool
-       logger     *zap.SugaredLogger
+
+       transfersMU sync.Mutex
+       transfers   map[int]map[[4]byte]*FileTransfer
+
+       Agreed bool
+       logger *zap.SugaredLogger
 }
 
 func (cc *ClientConn) sendAll(t int, fields ...Field) {
 }
 
 func (cc *ClientConn) sendAll(t int, fields ...Field) {
@@ -227,3 +206,56 @@ func sortedClients(unsortedClients map[uint16]*ClientConn) (clients []*ClientCon
        sort.Sort(byClientID(clients))
        return clients
 }
        sort.Sort(byClientID(clients))
        return clients
 }
+
+const userInfoTemplate = `Nickname:   %s
+Name:       %s
+Account:    %s
+Address:    %s
+
+-------- File Downloads ---------
+
+%s
+------- Folder Downloads --------
+
+%s
+--------- File Uploads ----------
+
+%s
+-------- Folder Uploads ---------
+
+%s
+------- Waiting Downloads -------
+
+%s
+`
+
+func formatDownloadList(fts map[[4]byte]*FileTransfer) (s string) {
+       if len(fts) == 0 {
+               return "None.\n"
+       }
+
+       for _, dl := range fts {
+               s += dl.String()
+       }
+
+       return s
+}
+
+func (cc *ClientConn) String() string {
+       cc.transfersMU.Lock()
+       defer cc.transfersMU.Unlock()
+       template := fmt.Sprintf(
+               userInfoTemplate,
+               cc.UserName,
+               cc.Account.Name,
+               cc.Account.Login,
+               cc.RemoteAddr,
+               formatDownloadList(cc.transfers[FileDownload]),
+               formatDownloadList(cc.transfers[FolderDownload]),
+               formatDownloadList(cc.transfers[FileUpload]),
+               formatDownloadList(cc.transfers[FolderUpload]),
+               "None.\n",
+       )
+
+       return strings.Replace(template, "\n", "\r", -1)
+}
index c7c1a62456e4c73162d99bbfdc8b4a2d4246ab43..ba4a1e1b3f12a621a6c8067bee4d61ca4e56e52c 100644 (file)
@@ -3,7 +3,10 @@ package hotline
 import (
        "encoding/binary"
        "fmt"
 import (
        "encoding/binary"
        "fmt"
+       "math"
+       "math/rand"
        "path/filepath"
        "path/filepath"
+       "sync"
 )
 
 // File transfer types
 )
 
 // File transfer types
@@ -16,23 +19,86 @@ const (
 )
 
 type FileTransfer struct {
 )
 
 type FileTransfer struct {
-       FileName        []byte
-       FilePath        []byte
-       ReferenceNumber []byte
-       Type            int
-       TransferSize    []byte // total size of all items in the folder. Only used in FolderUpload action
-       FolderItemCount []byte
-       BytesSent       int
-       clientID        uint16
-       fileResumeData  *FileResumeData
-       options         []byte
+       FileName         []byte
+       FilePath         []byte
+       ReferenceNumber  []byte
+       refNum           [4]byte
+       Type             int
+       TransferSize     []byte
+       FolderItemCount  []byte
+       fileResumeData   *FileResumeData
+       options          []byte
+       bytesSentCounter *WriteCounter
+       ClientConn       *ClientConn
 }
 
 }
 
+// WriteCounter counts the number of bytes written to it.
+type WriteCounter struct {
+       mux   sync.Mutex
+       Total int64 // Total # of bytes written
+}
+
+// Write implements the io.Writer interface.
+//
+// Always completes and never returns an error.
+func (wc *WriteCounter) Write(p []byte) (int, error) {
+       wc.mux.Lock()
+       defer wc.mux.Unlock()
+       n := len(p)
+       wc.Total += int64(n)
+       return n, nil
+}
+
+func (cc *ClientConn) newFileTransfer(transferType int, fileName, filePath, size []byte) *FileTransfer {
+       var transactionRef [4]byte
+       rand.Read(transactionRef[:])
+
+       ft := &FileTransfer{
+               FileName:         fileName,
+               FilePath:         filePath,
+               ReferenceNumber:  transactionRef[:],
+               refNum:           transactionRef,
+               Type:             transferType,
+               TransferSize:     size,
+               ClientConn:       cc,
+               bytesSentCounter: &WriteCounter{},
+       }
+
+       cc.transfersMU.Lock()
+       defer cc.transfersMU.Unlock()
+       cc.transfers[transferType][transactionRef] = ft
+
+       cc.Server.mux.Lock()
+       defer cc.Server.mux.Unlock()
+       cc.Server.fileTransfers[transactionRef] = ft
+
+       return ft
+}
+
+// String returns a string representation of a file transfer and its progress for display in the GetInfo window
+// Example:
+// MasterOfOrionII1.4.0. 0%   197.9M
 func (ft *FileTransfer) String() string {
 func (ft *FileTransfer) String() string {
-       percentComplete := 10
-       out := fmt.Sprintf("%s\t %v", ft.FileName, percentComplete)
+       trunc := fmt.Sprintf("%.21s", ft.FileName)
+       return fmt.Sprintf("%-21s %.3s%%  %6s\n", trunc, ft.percentComplete(), ft.formattedTransferSize())
+}
 
 
-       return out
+func (ft *FileTransfer) percentComplete() string {
+       ft.bytesSentCounter.mux.Lock()
+       defer ft.bytesSentCounter.mux.Unlock()
+       return fmt.Sprintf(
+               "%v",
+               math.RoundToEven(float64(ft.bytesSentCounter.Total)/float64(binary.BigEndian.Uint32(ft.TransferSize))*100),
+       )
+}
+
+func (ft *FileTransfer) formattedTransferSize() string {
+       sizeInKB := float32(binary.BigEndian.Uint32(ft.TransferSize)) / 1024
+       if sizeInKB > 1024 {
+               return fmt.Sprintf("%.1fM", sizeInKB/1024)
+       } else {
+               return fmt.Sprintf("%.0fK", sizeInKB)
+       }
 }
 
 func (ft *FileTransfer) ItemCount() int {
 }
 
 func (ft *FileTransfer) ItemCount() int {
index b59fc5c9926cdb468091fdc6b3c5f275abf67c4d..f3fd55947d97b8c75403f16c5037b65da2df3060 100644 (file)
@@ -105,7 +105,7 @@ func (f *fileWrapper) infoForkName() string {
        return fmt.Sprintf(infoForkNameTemplate, f.name)
 }
 
        return fmt.Sprintf(infoForkNameTemplate, f.name)
 }
 
-func (f *fileWrapper) rsrcForkWriter() (io.Writer, error) {
+func (f *fileWrapper) rsrcForkWriter() (io.WriteCloser, error) {
        file, err := os.OpenFile(f.rsrcPath, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
        file, err := os.OpenFile(f.rsrcPath, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
@@ -114,7 +114,7 @@ func (f *fileWrapper) rsrcForkWriter() (io.Writer, error) {
        return file, nil
 }
 
        return file, nil
 }
 
-func (f *fileWrapper) infoForkWriter() (io.Writer, error) {
+func (f *fileWrapper) infoForkWriter() (io.WriteCloser, error) {
        file, err := os.OpenFile(f.infoPath, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
        file, err := os.OpenFile(f.infoPath, os.O_CREATE|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
@@ -123,7 +123,7 @@ func (f *fileWrapper) infoForkWriter() (io.Writer, error) {
        return file, nil
 }
 
        return file, nil
 }
 
-func (f *fileWrapper) incFileWriter() (io.Writer, error) {
+func (f *fileWrapper) incFileWriter() (io.WriteCloser, error) {
        file, err := os.OpenFile(f.incompletePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
        file, err := os.OpenFile(f.incompletePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
        if err != nil {
                return nil, err
index 21b1316d4f233b0e36ae11e6d12b928e9d0f0bc6..e685afdbe7a76dee8e4b173d9702649800320f84 100644 (file)
@@ -43,12 +43,14 @@ const (
 var nostalgiaVersion = []byte{0, 0, 2, 0x2c} // version ID used by the Nostalgia client
 
 type Server struct {
 var nostalgiaVersion = []byte{0, 0, 2, 0x2c} // version ID used by the Nostalgia client
 
 type Server struct {
-       Port          int
-       Accounts      map[string]*Account
-       Agreement     []byte
-       Clients       map[uint16]*ClientConn
-       ThreadedNews  *ThreadedNews
-       FileTransfers map[uint32]*FileTransfer
+       Port         int
+       Accounts     map[string]*Account
+       Agreement    []byte
+       Clients      map[uint16]*ClientConn
+       ThreadedNews *ThreadedNews
+
+       fileTransfers map[[4]byte]*FileTransfer
+
        Config        *Config
        ConfigDir     string
        Logger        *zap.SugaredLogger
        Config        *Config
        ConfigDir     string
        Logger        *zap.SugaredLogger
@@ -214,7 +216,7 @@ func NewServer(configDir string, netPort int, logger *zap.SugaredLogger, FS File
                Accounts:      make(map[string]*Account),
                Config:        new(Config),
                Clients:       make(map[uint16]*ClientConn),
                Accounts:      make(map[string]*Account),
                Config:        new(Config),
                Clients:       make(map[uint16]*ClientConn),
-               FileTransfers: make(map[uint32]*FileTransfer),
+               fileTransfers: make(map[[4]byte]*FileTransfer),
                PrivateChats:  make(map[uint32]*PrivateChat),
                ConfigDir:     configDir,
                Logger:        logger,
                PrivateChats:  make(map[uint32]*PrivateChat),
                ConfigDir:     configDir,
                Logger:        logger,
@@ -354,10 +356,18 @@ func (s *Server) NewClientConn(conn io.ReadWriteCloser, remoteAddr string) *Clie
                Server:     s,
                Version:    &[]byte{},
                AutoReply:  []byte{},
                Server:     s,
                Version:    &[]byte{},
                AutoReply:  []byte{},
-               Transfers:  make(map[int][]*FileTransfer),
+               transfers:  map[int]map[[4]byte]*FileTransfer{},
                Agreed:     false,
                RemoteAddr: remoteAddr,
        }
                Agreed:     false,
                RemoteAddr: remoteAddr,
        }
+       clientConn.transfers = map[int]map[[4]byte]*FileTransfer{
+               FileDownload:   {},
+               FileUpload:     {},
+               FolderDownload: {},
+               FolderUpload:   {},
+               bannerDownload: {},
+       }
+
        *s.NextGuestID++
        ID := *s.NextGuestID
 
        *s.NextGuestID++
        ID := *s.NextGuestID
 
@@ -651,16 +661,6 @@ func (s *Server) handleNewConnection(ctx context.Context, conn io.ReadWriteClose
        }
 }
 
        }
 }
 
-// NewTransactionRef generates a random ID for the file transfer.  The Hotline client includes this ID
-// in the transfer request payload, and the file transfer server will use it to map the request
-// to a transfer
-func (s *Server) NewTransactionRef() []byte {
-       transactionRef := make([]byte, 4)
-       rand.Read(transactionRef)
-
-       return transactionRef
-}
-
 func (s *Server) NewPrivateChat(cc *ClientConn) []byte {
        s.mux.Lock()
        defer s.mux.Unlock()
 func (s *Server) NewPrivateChat(cc *ClientConn) []byte {
        s.mux.Lock()
        defer s.mux.Unlock()
@@ -696,56 +696,62 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                return err
        }
 
                return err
        }
 
-       transferRefNum := binary.BigEndian.Uint32(t.ReferenceNumber[:])
        defer func() {
                s.mux.Lock()
        defer func() {
                s.mux.Lock()
-               delete(s.FileTransfers, transferRefNum)
+               delete(s.fileTransfers, t.ReferenceNumber)
                s.mux.Unlock()
                s.mux.Unlock()
+
        }()
 
        s.mux.Lock()
        }()
 
        s.mux.Lock()
-       fileTransfer, ok := s.FileTransfers[transferRefNum]
+       fileTransfer, ok := s.fileTransfers[t.ReferenceNumber]
        s.mux.Unlock()
        if !ok {
                return errors.New("invalid transaction ID")
        }
 
        s.mux.Unlock()
        if !ok {
                return errors.New("invalid transaction ID")
        }
 
+       defer func() {
+               fileTransfer.ClientConn.transfersMU.Lock()
+               delete(fileTransfer.ClientConn.transfers[fileTransfer.Type], t.ReferenceNumber)
+               fileTransfer.ClientConn.transfersMU.Unlock()
+       }()
+
        rLogger := s.Logger.With(
                "remoteAddr", ctx.Value(contextKeyReq).(requestCtx).remoteAddr,
        rLogger := s.Logger.With(
                "remoteAddr", ctx.Value(contextKeyReq).(requestCtx).remoteAddr,
-               "xferID", transferRefNum,
+               "login", fileTransfer.ClientConn.Account.Login,
+               "name", string(fileTransfer.ClientConn.UserName),
        )
 
        )
 
+       fullPath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
+       if err != nil {
+               return err
+       }
+
        switch fileTransfer.Type {
        case bannerDownload:
                if err := s.bannerDownload(rwc); err != nil {
        switch fileTransfer.Type {
        case bannerDownload:
                if err := s.bannerDownload(rwc); err != nil {
+                       panic(err)
                        return err
                }
        case FileDownload:
                s.Stats.DownloadCounter += 1
 
                        return err
                }
        case FileDownload:
                s.Stats.DownloadCounter += 1
 
-               fullFilePath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
-               if err != nil {
-                       return err
-               }
-
                var dataOffset int64
                if fileTransfer.fileResumeData != nil {
                        dataOffset = int64(binary.BigEndian.Uint32(fileTransfer.fileResumeData.ForkInfoList[0].DataSize[:]))
                }
 
                var dataOffset int64
                if fileTransfer.fileResumeData != nil {
                        dataOffset = int64(binary.BigEndian.Uint32(fileTransfer.fileResumeData.ForkInfoList[0].DataSize[:]))
                }
 
-               fw, err := newFileWrapper(s.FS, fullFilePath, 0)
+               fw, err := newFileWrapper(s.FS, fullPath, 0)
                if err != nil {
                        return err
                }
 
                if err != nil {
                        return err
                }
 
-               rLogger.Infow("File download started", "filePath", fullFilePath, "transactionRef", fileTransfer.ReferenceNumber)
-
-               wr := bufio.NewWriterSize(rwc, 1460)
+               rLogger.Infow("File download started", "filePath", fullPath)
 
                // if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client
                if fileTransfer.options == nil {
                        // Start by sending flat file object to client
 
                // if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client
                if fileTransfer.options == nil {
                        // Start by sending flat file object to client
-                       if _, err := wr.Write(fw.ffo.BinaryMarshal()); err != nil {
+                       if _, err := rwc.Write(fw.ffo.BinaryMarshal()); err != nil {
                                return err
                        }
                }
                                return err
                        }
                }
@@ -755,23 +761,21 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                        return err
                }
 
                        return err
                }
 
-               if err := sendFile(wr, file, int(dataOffset)); err != nil {
+               br := bufio.NewReader(file)
+               if _, err := br.Discard(int(dataOffset)); err != nil {
                        return err
                }
 
                        return err
                }
 
-               if err := wr.Flush(); err != nil {
+               if _, err = io.Copy(rwc, io.TeeReader(br, fileTransfer.bytesSentCounter)); err != nil {
                        return err
                }
 
                        return err
                }
 
-               // if the client requested to resume transfer, do not send the resource fork, or it will be appended into the fileWrapper data
+               // if the client requested to resume transfer, do not send the resource fork header, or it will be appended into the fileWrapper data
                if fileTransfer.fileResumeData == nil {
                if fileTransfer.fileResumeData == nil {
-                       err = binary.Write(wr, binary.BigEndian, fw.rsrcForkHeader())
+                       err = binary.Write(rwc, binary.BigEndian, fw.rsrcForkHeader())
                        if err != nil {
                                return err
                        }
                        if err != nil {
                                return err
                        }
-                       if err := wr.Flush(); err != nil {
-                               return err
-                       }
                }
 
                rFile, err := fw.rsrcForkFile()
                }
 
                rFile, err := fw.rsrcForkFile()
@@ -779,23 +783,13 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                        return nil
                }
 
                        return nil
                }
 
-               err = sendFile(wr, rFile, int(dataOffset))
-               if err != nil {
-                       return err
-               }
-
-               if err := wr.Flush(); err != nil {
+               if _, err = io.Copy(rwc, io.TeeReader(rFile, fileTransfer.bytesSentCounter)); err != nil {
                        return err
                }
 
        case FileUpload:
                s.Stats.UploadCounter += 1
 
                        return err
                }
 
        case FileUpload:
                s.Stats.UploadCounter += 1
 
-               destinationFile, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
-               if err != nil {
-                       return err
-               }
-
                var file *os.File
 
                // A file upload has three possible cases:
                var file *os.File
 
                // A file upload has three possible cases:
@@ -805,28 +799,24 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                //  We have to infer which case applies by inspecting what is already on the filesystem
 
                // 1) Check for existing file:
                //  We have to infer which case applies by inspecting what is already on the filesystem
 
                // 1) Check for existing file:
-               _, err = os.Stat(destinationFile)
+               _, err = os.Stat(fullPath)
                if err == nil {
                if err == nil {
-                       // If found, that means this upload is intended to replace the file
-                       if err = os.Remove(destinationFile); err != nil {
-                               return err
-                       }
-                       file, err = os.Create(destinationFile + incompleteFileSuffix)
+                       return errors.New("existing file found at " + fullPath)
                }
                if errors.Is(err, fs.ErrNotExist) {
                        // If not found, open or create a new .incomplete file
                }
                if errors.Is(err, fs.ErrNotExist) {
                        // If not found, open or create a new .incomplete file
-                       file, err = os.OpenFile(destinationFile+incompleteFileSuffix, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
+                       file, err = os.OpenFile(fullPath+incompleteFileSuffix, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
                        if err != nil {
                                return err
                        }
                }
 
                        if err != nil {
                                return err
                        }
                }
 
-               f, err := newFileWrapper(s.FS, destinationFile, 0)
+               f, err := newFileWrapper(s.FS, fullPath, 0)
                if err != nil {
                        return err
                }
 
                if err != nil {
                        return err
                }
 
-               s.Logger.Infow("File upload started", "transactionRef", fileTransfer.ReferenceNumber, "dstFile", destinationFile)
+               rLogger.Infow("File upload started", "dstFile", fullPath)
 
                rForkWriter := io.Discard
                iForkWriter := io.Discard
 
                rForkWriter := io.Discard
                iForkWriter := io.Discard
@@ -842,19 +832,19 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                        }
                }
 
                        }
                }
 
-               if err := receiveFile(rwc, file, rForkWriter, iForkWriter); err != nil {
-                       return err
+               if err := receiveFile(rwc, file, rForkWriter, iForkWriter, fileTransfer.bytesSentCounter); err != nil {
+                       s.Logger.Error(err)
                }
 
                if err := file.Close(); err != nil {
                        return err
                }
 
                }
 
                if err := file.Close(); err != nil {
                        return err
                }
 
-               if err := s.FS.Rename(destinationFile+".incomplete", destinationFile); err != nil {
+               if err := s.FS.Rename(fullPath+".incomplete", fullPath); err != nil {
                        return err
                }
 
                        return err
                }
 
-               s.Logger.Infow("File upload complete", "transactionRef", fileTransfer.ReferenceNumber, "dstFile", destinationFile)
+               rLogger.Infow("File upload complete", "dstFile", fullPath)
        case FolderDownload:
                // Folder Download flow:
                // 1. Get filePath from the transfer
        case FolderDownload:
                // Folder Download flow:
                // 1. Get filePath from the transfer
@@ -883,14 +873,9 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                //
                // This notifies the server to send the next item header
 
                //
                // This notifies the server to send the next item header
 
-               fullFilePath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
-               if err != nil {
-                       return err
-               }
+               basePathLen := len(fullPath)
 
 
-               basePathLen := len(fullFilePath)
-
-               s.Logger.Infow("Start folder download", "path", fullFilePath, "ReferenceNumber", fileTransfer.ReferenceNumber)
+               rLogger.Infow("Start folder download", "path", fullPath)
 
                nextAction := make([]byte, 2)
                if _, err := io.ReadFull(rwc, nextAction); err != nil {
 
                nextAction := make([]byte, 2)
                if _, err := io.ReadFull(rwc, nextAction); err != nil {
@@ -898,7 +883,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                }
 
                i := 0
                }
 
                i := 0
-               err = filepath.Walk(fullFilePath+"/", func(path string, info os.FileInfo, err error) error {
+               err = filepath.Walk(fullPath+"/", func(path string, info os.FileInfo, err error) error {
                        s.Stats.DownloadCounter += 1
                        i += 1
 
                        s.Stats.DownloadCounter += 1
                        i += 1
 
@@ -917,7 +902,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                        }
 
                        subPath := path[basePathLen+1:]
                        }
 
                        subPath := path[basePathLen+1:]
-                       s.Logger.Infow("Sending fileheader", "i", i, "path", path, "fullFilePath", fullFilePath, "subPath", subPath, "IsDir", info.IsDir())
+                       rLogger.Debugw("Sending fileheader", "i", i, "path", path, "fullFilePath", fullPath, "subPath", subPath, "IsDir", info.IsDir())
 
                        if i == 1 {
                                return nil
 
                        if i == 1 {
                                return nil
@@ -936,7 +921,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                return err
                        }
 
                                return err
                        }
 
-                       s.Logger.Infow("Client folder download action", "action", fmt.Sprintf("%X", nextAction[0:2]))
+                       rLogger.Debugw("Client folder download action", "action", fmt.Sprintf("%X", nextAction[0:2]))
 
                        var dataOffset int64
 
 
                        var dataOffset int64
 
@@ -968,9 +953,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                return nil
                        }
 
                                return nil
                        }
 
-                       s.Logger.Infow("File download started",
+                       rLogger.Infow("File download started",
                                "fileName", info.Name(),
                                "fileName", info.Name(),
-                               "transactionRef", fileTransfer.ReferenceNumber,
                                "TransferSize", fmt.Sprintf("%x", hlFile.ffo.TransferSize(dataOffset)),
                        )
 
                                "TransferSize", fmt.Sprintf("%x", hlFile.ffo.TransferSize(dataOffset)),
                        )
 
@@ -992,8 +976,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                        }
 
                        // wr := bufio.NewWriterSize(rwc, 1460)
                        }
 
                        // wr := bufio.NewWriterSize(rwc, 1460)
-                       err = sendFile(rwc, file, int(dataOffset))
-                       if err != nil {
+                       if _, err = io.Copy(rwc, io.TeeReader(file, fileTransfer.bytesSentCounter)); err != nil {
                                return err
                        }
 
                                return err
                        }
 
@@ -1008,8 +991,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                        return err
                                }
 
                                        return err
                                }
 
-                               err = sendFile(rwc, rFile, int(dataOffset))
-                               if err != nil {
+                               if _, err = io.Copy(rwc, io.TeeReader(rFile, fileTransfer.bytesSentCounter)); err != nil {
                                        return err
                                }
                        }
                                        return err
                                }
                        }
@@ -1027,22 +1009,16 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                }
 
        case FolderUpload:
                }
 
        case FolderUpload:
-               dstPath, err := readPath(s.Config.FileRoot, fileTransfer.FilePath, fileTransfer.FileName)
-               if err != nil {
-                       return err
-               }
-
-               s.Logger.Infow(
+               rLogger.Infow(
                        "Folder upload started",
                        "Folder upload started",
-                       "transactionRef", fileTransfer.ReferenceNumber,
-                       "dstPath", dstPath,
-                       "TransferSize", fmt.Sprintf("%x", fileTransfer.TransferSize),
+                       "dstPath", fullPath,
+                       "TransferSize", binary.BigEndian.Uint32(fileTransfer.TransferSize),
                        "FolderItemCount", fileTransfer.FolderItemCount,
                )
 
                // Check if the target folder exists.  If not, create it.
                        "FolderItemCount", fileTransfer.FolderItemCount,
                )
 
                // Check if the target folder exists.  If not, create it.
-               if _, err := s.FS.Stat(dstPath); os.IsNotExist(err) {
-                       if err := s.FS.Mkdir(dstPath, 0777); err != nil {
+               if _, err := s.FS.Stat(fullPath); os.IsNotExist(err) {
+                       if err := s.FS.Mkdir(fullPath, 0777); err != nil {
                                return err
                        }
                }
                                return err
                        }
                }
@@ -1074,17 +1050,16 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                return err
                        }
 
                                return err
                        }
 
-                       s.Logger.Infow(
+                       rLogger.Infow(
                                "Folder upload continued",
                                "Folder upload continued",
-                               "transactionRef", fmt.Sprintf("%x", fileTransfer.ReferenceNumber),
                                "FormattedPath", fu.FormattedPath(),
                                "IsFolder", fmt.Sprintf("%x", fu.IsFolder),
                                "PathItemCount", binary.BigEndian.Uint16(fu.PathItemCount[:]),
                        )
 
                        if fu.IsFolder == [2]byte{0, 1} {
                                "FormattedPath", fu.FormattedPath(),
                                "IsFolder", fmt.Sprintf("%x", fu.IsFolder),
                                "PathItemCount", binary.BigEndian.Uint16(fu.PathItemCount[:]),
                        )
 
                        if fu.IsFolder == [2]byte{0, 1} {
-                               if _, err := os.Stat(filepath.Join(dstPath, fu.FormattedPath())); os.IsNotExist(err) {
-                                       if err := os.Mkdir(filepath.Join(dstPath, fu.FormattedPath()), 0777); err != nil {
+                               if _, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath())); os.IsNotExist(err) {
+                                       if err := os.Mkdir(filepath.Join(fullPath, fu.FormattedPath()), 0777); err != nil {
                                                return err
                                        }
                                }
                                                return err
                                        }
                                }
@@ -1097,7 +1072,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                nextAction := dlFldrActionSendFile
 
                                // Check if we have the full file already.  If so, send dlFldrAction_NextFile to client to skip.
                                nextAction := dlFldrActionSendFile
 
                                // Check if we have the full file already.  If so, send dlFldrAction_NextFile to client to skip.
-                               _, err = os.Stat(filepath.Join(dstPath, fu.FormattedPath()))
+                               _, err = os.Stat(filepath.Join(fullPath, fu.FormattedPath()))
                                if err != nil && !errors.Is(err, fs.ErrNotExist) {
                                        return err
                                }
                                if err != nil && !errors.Is(err, fs.ErrNotExist) {
                                        return err
                                }
@@ -1106,7 +1081,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                }
 
                                //  Check if we have a partial file already.  If so, send dlFldrAction_ResumeFile to client to resume upload.
                                }
 
                                //  Check if we have a partial file already.  If so, send dlFldrAction_ResumeFile to client to resume upload.
-                               incompleteFile, err := os.Stat(filepath.Join(dstPath, fu.FormattedPath()+incompleteFileSuffix))
+                               incompleteFile, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath()+incompleteFileSuffix))
                                if err != nil && !errors.Is(err, fs.ErrNotExist) {
                                        return err
                                }
                                if err != nil && !errors.Is(err, fs.ErrNotExist) {
                                        return err
                                }
@@ -1125,7 +1100,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                        offset := make([]byte, 4)
                                        binary.BigEndian.PutUint32(offset, uint32(incompleteFile.Size()))
 
                                        offset := make([]byte, 4)
                                        binary.BigEndian.PutUint32(offset, uint32(incompleteFile.Size()))
 
-                                       file, err := os.OpenFile(dstPath+"/"+fu.FormattedPath()+incompleteFileSuffix, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+                                       file, err := os.OpenFile(fullPath+"/"+fu.FormattedPath()+incompleteFileSuffix, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
                                        if err != nil {
                                                return err
                                        }
                                        if err != nil {
                                                return err
                                        }
@@ -1145,11 +1120,11 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                                return err
                                        }
 
                                                return err
                                        }
 
-                                       if err := receiveFile(rwc, file, ioutil.Discard, ioutil.Discard); err != nil {
+                                       if err := receiveFile(rwc, file, ioutil.Discard, ioutil.Discard, fileTransfer.bytesSentCounter); err != nil {
                                                s.Logger.Error(err)
                                        }
 
                                                s.Logger.Error(err)
                                        }
 
-                                       err = os.Rename(dstPath+"/"+fu.FormattedPath()+".incomplete", dstPath+"/"+fu.FormattedPath())
+                                       err = os.Rename(fullPath+"/"+fu.FormattedPath()+".incomplete", fullPath+"/"+fu.FormattedPath())
                                        if err != nil {
                                                return err
                                        }
                                        if err != nil {
                                                return err
                                        }
@@ -1159,14 +1134,14 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                                return err
                                        }
 
                                                return err
                                        }
 
-                                       filePath := filepath.Join(dstPath, fu.FormattedPath())
+                                       filePath := filepath.Join(fullPath, fu.FormattedPath())
 
                                        hlFile, err := newFileWrapper(s.FS, filePath, 0)
                                        if err != nil {
                                                return err
                                        }
 
 
                                        hlFile, err := newFileWrapper(s.FS, filePath, 0)
                                        if err != nil {
                                                return err
                                        }
 
-                                       s.Logger.Infow("Starting file transfer", "path", filePath, "fileNum", i+1, "fileSize", binary.BigEndian.Uint32(fileSize))
+                                       rLogger.Infow("Starting file transfer", "path", filePath, "fileNum", i+1, "fileSize", binary.BigEndian.Uint32(fileSize))
 
                                        incWriter, err := hlFile.incFileWriter()
                                        if err != nil {
 
                                        incWriter, err := hlFile.incFileWriter()
                                        if err != nil {
@@ -1186,10 +1161,10 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                                        return err
                                                }
                                        }
                                                        return err
                                                }
                                        }
-                                       if err := receiveFile(rwc, incWriter, rForkWriter, iForkWriter); err != nil {
+                                       if err := receiveFile(rwc, incWriter, rForkWriter, iForkWriter, fileTransfer.bytesSentCounter); err != nil {
                                                return err
                                        }
                                                return err
                                        }
-                                       // _ = newFile.Close()
+
                                        if err := os.Rename(filePath+".incomplete", filePath); err != nil {
                                                return err
                                        }
                                        if err := os.Rename(filePath+".incomplete", filePath); err != nil {
                                                return err
                                        }
@@ -1201,7 +1176,7 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro
                                }
                        }
                }
                                }
                        }
                }
-               s.Logger.Infof("Folder upload complete")
+               rLogger.Infof("Folder upload complete")
        }
 
        return nil
        }
 
        return nil
index d3e43250aeb840af203270a4312c04a997a274ba..bf608e1c3da5992255d55b6a1d44ff74ba143b70 100644 (file)
@@ -32,7 +32,7 @@ func TestServer_handleFileTransfer(t *testing.T) {
                Agreement     []byte
                Clients       map[uint16]*ClientConn
                ThreadedNews  *ThreadedNews
                Agreement     []byte
                Clients       map[uint16]*ClientConn
                ThreadedNews  *ThreadedNews
-               FileTransfers map[uint32]*FileTransfer
+               fileTransfers map[[4]byte]*FileTransfer
                Config        *Config
                ConfigDir     string
                Logger        *zap.SugaredLogger
                Config        *Config
                ConfigDir     string
                Logger        *zap.SugaredLogger
@@ -116,12 +116,24 @@ func TestServer_handleFileTransfer(t *testing.T) {
                                        }()},
                                Logger: NewTestLogger(),
                                Stats:  &Stats{},
                                        }()},
                                Logger: NewTestLogger(),
                                Stats:  &Stats{},
-                               FileTransfers: map[uint32]*FileTransfer{
-                                       uint32(5): {
+                               fileTransfers: map[[4]byte]*FileTransfer{
+                                       [4]byte{0, 0, 0, 5}: {
                                                ReferenceNumber: []byte{0, 0, 0, 5},
                                                Type:            FileDownload,
                                                FileName:        []byte("testfile-8b"),
                                                FilePath:        []byte{},
                                                ReferenceNumber: []byte{0, 0, 0, 5},
                                                Type:            FileDownload,
                                                FileName:        []byte("testfile-8b"),
                                                FilePath:        []byte{},
+                                               ClientConn: &ClientConn{
+                                                       Account: &Account{
+                                                               Login: "foo",
+                                                       },
+                                                       transfersMU: sync.Mutex{},
+                                                       transfers: map[int]map[[4]byte]*FileTransfer{
+                                                               FileDownload: {
+                                                                       [4]byte{0, 0, 0, 5}: &FileTransfer{},
+                                                               },
+                                                       },
+                                               },
+                                               bytesSentCounter: &WriteCounter{},
                                        },
                                },
                        },
                                        },
                                },
                        },
@@ -168,7 +180,7 @@ func TestServer_handleFileTransfer(t *testing.T) {
                                Agreement:     tt.fields.Agreement,
                                Clients:       tt.fields.Clients,
                                ThreadedNews:  tt.fields.ThreadedNews,
                                Agreement:     tt.fields.Agreement,
                                Clients:       tt.fields.Clients,
                                ThreadedNews:  tt.fields.ThreadedNews,
-                               FileTransfers: tt.fields.FileTransfers,
+                               fileTransfers: tt.fields.fileTransfers,
                                Config:        tt.fields.Config,
                                ConfigDir:     tt.fields.ConfigDir,
                                Logger:        tt.fields.Logger,
                                Config:        tt.fields.Config,
                                ConfigDir:     tt.fields.ConfigDir,
                                Logger:        tt.fields.Logger,
index f6fa2325bd63eada2a13fa918d46ff420e2cbaf3..735e155a951dd81befd5d4863506afe28d047245 100644 (file)
@@ -87,7 +87,7 @@ var TransactionHandlers = map[uint16]TransactionType{
        },
        tranGetClientInfoText: {
                Name:    "tranGetClientInfoText",
        },
        tranGetClientInfoText: {
                Name:    "tranGetClientInfoText",
-               Handler: HandleGetClientConnInfoText,
+               Handler: HandleGetClientInfoText,
        },
        tranGetFileInfo: {
                Name:    "tranGetFileInfo",
        },
        tranGetFileInfo: {
                Name:    "tranGetFileInfo",
@@ -867,9 +867,17 @@ func byteToInt(bytes []byte) (int, error) {
        return 0, errors.New("unknown byte length")
 }
 
        return 0, errors.New("unknown byte length")
 }
 
-func HandleGetClientConnInfoText(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
+// HandleGetClientInfoText returns user information for the specific user.
+//
+// Fields used in the request:
+// 103 User ID
+//
+// Fields used in the reply:
+// 102 User name
+// 101 Data            User info text string
+func HandleGetClientInfoText(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
        if !authorize(cc.Account.Access, accessGetClientInfo) {
        if !authorize(cc.Account.Access, accessGetClientInfo) {
-               res = append(res, cc.NewErrReply(t, "You are not allowed to get client info"))
+               res = append(res, cc.NewErrReply(t, "You are not allowed to get client info."))
                return res, err
        }
 
                return res, err
        }
 
@@ -877,55 +885,11 @@ func HandleGetClientConnInfoText(cc *ClientConn, t *Transaction) (res []Transact
 
        clientConn := cc.Server.Clients[uint16(clientID)]
        if clientConn == nil {
 
        clientConn := cc.Server.Clients[uint16(clientID)]
        if clientConn == nil {
-               return res, errors.New("invalid client")
+               return append(res, cc.NewErrReply(t, "User not found.")), err
        }
 
        }
 
-       // TODO: Implement non-hardcoded values
-       template := `Nickname:   %s
-Name:       %s
-Account:    %s
-Address:    %s
-
--------- File Downloads ---------
-
-%s
-
-------- Folder Downloads --------
-
-None.
-
---------- File Uploads ----------
-
-None.
-
--------- Folder Uploads ---------
-
-None.
-
-------- Waiting Downloads -------
-
-None.
-
-       `
-
-       activeDownloads := clientConn.Transfers[FileDownload]
-       activeDownloadList := "None."
-       for _, dl := range activeDownloads {
-               activeDownloadList += dl.String() + "\n"
-       }
-
-       template = fmt.Sprintf(
-               template,
-               clientConn.UserName,
-               clientConn.Account.Name,
-               clientConn.Account.Login,
-               clientConn.RemoteAddr,
-               activeDownloadList,
-       )
-       template = strings.Replace(template, "\n", "\r", -1)
-
        res = append(res, cc.NewReply(t,
        res = append(res, cc.NewReply(t,
-               NewField(fieldData, []byte(template)),
+               NewField(fieldData, []byte(clientConn.String())),
                NewField(fieldUserName, clientConn.UserName),
        ))
        return res, err
                NewField(fieldUserName, clientConn.UserName),
        ))
        return res, err
@@ -1403,15 +1367,9 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                return res, err
        }
 
                return res, err
        }
 
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
+       xferSize := hlFile.ffo.TransferSize(0)
 
 
-       ft := &FileTransfer{
-               FileName:        fileName,
-               FilePath:        filePath,
-               ReferenceNumber: transactionRef,
-               Type:            FileDownload,
-       }
+       ft := cc.newFileTransfer(FileDownload, fileName, filePath, xferSize)
 
        // TODO: refactor to remove this
        if resumeData != nil {
 
        // TODO: refactor to remove this
        if resumeData != nil {
@@ -1422,8 +1380,6 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                ft.fileResumeData = &frd
        }
 
                ft.fileResumeData = &frd
        }
 
-       xferSize := hlFile.ffo.TransferSize(0)
-
        // Optional field for when a HL v1.5+ client requests file preview
        // Used only for TEXT, JPEG, GIFF, BMP or PICT files
        // The value will always be 2
        // Optional field for when a HL v1.5+ client requests file preview
        // Used only for TEXT, JPEG, GIFF, BMP or PICT files
        // The value will always be 2
@@ -1432,14 +1388,8 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err
                xferSize = hlFile.ffo.FlatFileDataForkHeader.DataSize[:]
        }
 
                xferSize = hlFile.ffo.FlatFileDataForkHeader.DataSize[:]
        }
 
-       cc.Server.mux.Lock()
-       defer cc.Server.mux.Unlock()
-       cc.Server.FileTransfers[data] = ft
-
-       cc.Transfers[FileDownload] = append(cc.Transfers[FileDownload], ft)
-
        res = append(res, cc.NewReply(t,
        res = append(res, cc.NewReply(t,
-               NewField(fieldRefNum, transactionRef),
+               NewField(fieldRefNum, ft.refNum[:]),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
                NewField(fieldTransferSize, xferSize),
                NewField(fieldFileSize, hlFile.ffo.FlatFileDataForkHeader.DataSize[:]),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
                NewField(fieldTransferSize, xferSize),
                NewField(fieldFileSize, hlFile.ffo.FlatFileDataForkHeader.DataSize[:]),
@@ -1455,26 +1405,6 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
                return res, err
        }
 
                return res, err
        }
 
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
-       fileTransfer := &FileTransfer{
-               FileName:        t.GetField(fieldFileName).Data,
-               FilePath:        t.GetField(fieldFilePath).Data,
-               ReferenceNumber: transactionRef,
-               Type:            FolderDownload,
-       }
-       cc.Server.mux.Lock()
-       cc.Server.FileTransfers[data] = fileTransfer
-       cc.Server.mux.Unlock()
-       cc.Transfers[FolderDownload] = append(cc.Transfers[FolderDownload], fileTransfer)
-
-       var fp FilePath
-       err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data)
-       if err != nil {
-               return res, err
-       }
-
        fullFilePath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFilePath).Data, t.GetField(fieldFileName).Data)
        if err != nil {
                return res, err
        fullFilePath, err := readPath(cc.Server.Config.FileRoot, t.GetField(fieldFilePath).Data, t.GetField(fieldFileName).Data)
        if err != nil {
                return res, err
@@ -1488,8 +1418,17 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
        if err != nil {
                return res, err
        }
        if err != nil {
                return res, err
        }
+
+       fileTransfer := cc.newFileTransfer(FolderDownload, t.GetField(fieldFileName).Data, t.GetField(fieldFilePath).Data, transferSize)
+
+       var fp FilePath
+       err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data)
+       if err != nil {
+               return res, err
+       }
+
        res = append(res, cc.NewReply(t,
        res = append(res, cc.NewReply(t,
-               NewField(fieldRefNum, transactionRef),
+               NewField(fieldRefNum, fileTransfer.ReferenceNumber),
                NewField(fieldTransferSize, transferSize),
                NewField(fieldFolderItemCount, itemCount),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
                NewField(fieldTransferSize, transferSize),
                NewField(fieldFolderItemCount, itemCount),
                NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count
@@ -1505,9 +1444,6 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er
 // 220 Folder item count
 // 204 File transfer options   "Optional Currently set to 1" (TODO: ??)
 func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
 // 220 Folder item count
 // 204 File transfer options   "Optional Currently set to 1" (TODO: ??)
 func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
        var fp FilePath
        if t.GetField(fieldFilePath).Data != nil {
                if err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data); err != nil {
        var fp FilePath
        if t.GetField(fieldFilePath).Data != nil {
                if err = fp.UnmarshalBinary(t.GetField(fieldFilePath).Data); err != nil {
@@ -1523,17 +1459,15 @@ func HandleUploadFolder(cc *ClientConn, t *Transaction) (res []Transaction, err
                }
        }
 
                }
        }
 
-       fileTransfer := &FileTransfer{
-               FileName:        t.GetField(fieldFileName).Data,
-               FilePath:        t.GetField(fieldFilePath).Data,
-               ReferenceNumber: transactionRef,
-               Type:            FolderUpload,
-               FolderItemCount: t.GetField(fieldFolderItemCount).Data,
-               TransferSize:    t.GetField(fieldTransferSize).Data,
-       }
-       cc.Server.FileTransfers[data] = fileTransfer
+       fileTransfer := cc.newFileTransfer(FolderUpload,
+               t.GetField(fieldFileName).Data,
+               t.GetField(fieldFilePath).Data,
+               t.GetField(fieldTransferSize).Data,
+       )
+
+       fileTransfer.FolderItemCount = t.GetField(fieldFolderItemCount).Data
 
 
-       res = append(res, cc.NewReply(t, NewField(fieldRefNum, transactionRef)))
+       res = append(res, cc.NewReply(t, NewField(fieldRefNum, fileTransfer.ReferenceNumber)))
        return res, err
 }
 
        return res, err
 }
 
@@ -1552,11 +1486,8 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
        fileName := t.GetField(fieldFileName).Data
        filePath := t.GetField(fieldFilePath).Data
 
        fileName := t.GetField(fieldFileName).Data
        filePath := t.GetField(fieldFilePath).Data
-
        transferOptions := t.GetField(fieldFileTransferOptions).Data
        transferOptions := t.GetField(fieldFileTransferOptions).Data
-
-       // TODO: is this field useful for anything?
-       // transferSize := t.GetField(fieldTransferSize).Data
+       transferSize := t.GetField(fieldTransferSize).Data // not sent for resume
 
        var fp FilePath
        if filePath != nil {
 
        var fp FilePath
        if filePath != nil {
@@ -1572,27 +1503,22 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
                        return res, err
                }
        }
                        return res, err
                }
        }
+       fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
+       if err != nil {
+               return res, err
+       }
 
 
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
-       cc.Server.mux.Lock()
-       cc.Server.FileTransfers[data] = &FileTransfer{
-               FileName:        fileName,
-               FilePath:        filePath,
-               ReferenceNumber: transactionRef,
-               Type:            FileUpload,
+       if _, err := cc.Server.FS.Stat(fullFilePath); err == nil {
+               res = append(res, cc.NewErrReply(t, fmt.Sprintf("Cannot accept upload because there is already a file named \"%v\".  Try choosing a different name.", string(fileName))))
+               return res, err
        }
        }
-       cc.Server.mux.Unlock()
 
 
-       replyT := cc.NewReply(t, NewField(fieldRefNum, transactionRef))
+       ft := cc.newFileTransfer(FileUpload, fileName, filePath, transferSize)
+
+       replyT := cc.NewReply(t, NewField(fieldRefNum, ft.ReferenceNumber))
 
        // client has requested to resume a partially transferred file
        if transferOptions != nil {
 
        // client has requested to resume a partially transferred file
        if transferOptions != nil {
-               fullFilePath, err := readPath(cc.Server.Config.FileRoot, filePath, fileName)
-               if err != nil {
-                       return res, err
-               }
 
                fileInfo, err := cc.Server.FS.Stat(fullFilePath + incompleteFileSuffix)
                if err != nil {
 
                fileInfo, err := cc.Server.FS.Stat(fullFilePath + incompleteFileSuffix)
                if err != nil {
@@ -1608,6 +1534,8 @@ func HandleUploadFile(cc *ClientConn, t *Transaction) (res []Transaction, err er
 
                b, _ := fileResumeData.BinaryMarshal()
 
 
                b, _ := fileResumeData.BinaryMarshal()
 
+               ft.TransferSize = offset
+
                replyT.Fields = append(replyT.Fields, NewField(fieldFileResumeData, b))
        }
 
                replyT.Fields = append(replyT.Fields, NewField(fieldFileResumeData, b))
        }
 
@@ -1937,29 +1865,18 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err
 }
 
 func HandleDownloadBanner(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
 }
 
 func HandleDownloadBanner(cc *ClientConn, t *Transaction) (res []Transaction, err error) {
-       transactionRef := cc.Server.NewTransactionRef()
-       data := binary.BigEndian.Uint32(transactionRef)
-
-       ft := &FileTransfer{
-               ReferenceNumber: transactionRef,
-               Type:            bannerDownload,
-       }
-
        fi, err := cc.Server.FS.Stat(filepath.Join(cc.Server.ConfigDir, cc.Server.Config.BannerFile))
        if err != nil {
                return res, err
        }
 
        fi, err := cc.Server.FS.Stat(filepath.Join(cc.Server.ConfigDir, cc.Server.Config.BannerFile))
        if err != nil {
                return res, err
        }
 
-       size := make([]byte, 4)
-       binary.BigEndian.PutUint32(size, uint32(fi.Size()))
+       ft := cc.newFileTransfer(bannerDownload, []byte{}, []byte{}, make([]byte, 4))
 
 
-       cc.Server.mux.Lock()
-       defer cc.Server.mux.Unlock()
-       cc.Server.FileTransfers[data] = ft
+       binary.BigEndian.PutUint32(ft.TransferSize, uint32(fi.Size()))
 
        res = append(res, cc.NewReply(t,
 
        res = append(res, cc.NewReply(t,
-               NewField(fieldRefNum, transactionRef),
-               NewField(fieldTransferSize, size),
+               NewField(fieldRefNum, ft.refNum[:]),
+               NewField(fieldTransferSize, ft.TransferSize),
        ))
 
        return res, err
        ))
 
        return res, err
index 424a8e5180e67b3c40699ffb23c791a48f35adde..e379f096c65ce7bb114bfa079cf80a12c4dfe007 100644 (file)
@@ -978,7 +978,13 @@ func TestHandleUploadFile(t *testing.T) {
                        args: args{
                                cc: &ClientConn{
                                        Server: &Server{
                        args: args{
                                cc: &ClientConn{
                                        Server: &Server{
-                                               FileTransfers: map[uint32]*FileTransfer{},
+                                               FS:            &OSFileStore{},
+                                               fileTransfers: map[[4]byte]*FileTransfer{},
+                                               Config: &Config{
+                                                       FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
+                                               }},
+                                       transfers: map[int]map[[4]byte]*FileTransfer{
+                                               FileUpload: {},
                                        },
                                        Account: &Account{
                                                Access: func() *[]byte {
                                        },
                                        Account: &Account{
                                                Access: func() *[]byte {
@@ -1026,9 +1032,6 @@ func TestHandleUploadFile(t *testing.T) {
                                                        return &access
                                                }(),
                                        },
                                                        return &access
                                                }(),
                                        },
-                                       Server: &Server{
-                                               FileTransfers: map[uint32]*FileTransfer{},
-                                       },
                                },
                                t: NewTransaction(
                                        tranUploadFile, &[]byte{0, 1},
                                },
                                t: NewTransaction(
                                        tranUploadFile, &[]byte{0, 1},
@@ -1744,7 +1747,9 @@ func TestHandleDownloadFile(t *testing.T) {
                        name: "with a valid file",
                        args: args{
                                cc: &ClientConn{
                        name: "with a valid file",
                        args: args{
                                cc: &ClientConn{
-                                       Transfers: make(map[int][]*FileTransfer),
+                                       transfers: map[int]map[[4]byte]*FileTransfer{
+                                               FileDownload: {},
+                                       },
                                        Account: &Account{
                                                Access: func() *[]byte {
                                                        var bits accessBitmap
                                        Account: &Account{
                                                Access: func() *[]byte {
                                                        var bits accessBitmap
@@ -1755,7 +1760,7 @@ func TestHandleDownloadFile(t *testing.T) {
                                        },
                                        Server: &Server{
                                                FS:            &OSFileStore{},
                                        },
                                        Server: &Server{
                                                FS:            &OSFileStore{},
-                                               FileTransfers: make(map[uint32]*FileTransfer),
+                                               fileTransfers: map[[4]byte]*FileTransfer{},
                                                Config: &Config{
                                                        FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
                                                },
                                                Config: &Config{
                                                        FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
                                                },
@@ -1790,8 +1795,9 @@ func TestHandleDownloadFile(t *testing.T) {
                        name: "when client requests to resume 1k test file at offset 256",
                        args: args{
                                cc: &ClientConn{
                        name: "when client requests to resume 1k test file at offset 256",
                        args: args{
                                cc: &ClientConn{
-                                       Transfers: make(map[int][]*FileTransfer),
-                                       Account: &Account{
+                                       transfers: map[int]map[[4]byte]*FileTransfer{
+                                               FileDownload: {},
+                                       }, Account: &Account{
                                                Access: func() *[]byte {
                                                        var bits accessBitmap
                                                        bits.Set(accessDownloadFile)
                                                Access: func() *[]byte {
                                                        var bits accessBitmap
                                                        bits.Set(accessDownloadFile)
@@ -1801,6 +1807,7 @@ func TestHandleDownloadFile(t *testing.T) {
                                        },
                                        Server: &Server{
                                                FS: &OSFileStore{},
                                        },
                                        Server: &Server{
                                                FS: &OSFileStore{},
+
                                                // FS: func() *MockFileStore {
                                                //      path, _ := os.Getwd()
                                                //      testFile, err := os.Open(path + "/test/config/Files/testfile-1k")
                                                // FS: func() *MockFileStore {
                                                //      path, _ := os.Getwd()
                                                //      testFile, err := os.Open(path + "/test/config/Files/testfile-1k")
@@ -1818,7 +1825,7 @@ func TestHandleDownloadFile(t *testing.T) {
                                                //
                                                //      return mfs
                                                // }(),
                                                //
                                                //      return mfs
                                                // }(),
-                                               FileTransfers: make(map[uint32]*FileTransfer),
+                                               fileTransfers: map[[4]byte]*FileTransfer{},
                                                Config: &Config{
                                                        FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
                                                },
                                                Config: &Config{
                                                        FileRoot: func() string { path, _ := os.Getwd(); return path + "/test/config/Files" }(),
                                                },
@@ -2611,3 +2618,149 @@ func TestHandleGetFileNameList(t *testing.T) {
                })
        }
 }
                })
        }
 }
+
+func TestHandleGetClientInfoText(t *testing.T) {
+       type args struct {
+               cc *ClientConn
+               t  *Transaction
+       }
+       tests := []struct {
+               name    string
+               args    args
+               wantRes []Transaction
+               wantErr assert.ErrorAssertionFunc
+       }{
+               {
+                       name: "when user does not have required permission",
+                       args: args{
+                               cc: &ClientConn{
+                                       Account: &Account{
+                                               Access: func() *[]byte {
+                                                       var bits accessBitmap
+                                                       access := bits[:]
+                                                       return &access
+                                               }(),
+                                       },
+                                       Server: &Server{
+                                               Accounts: map[string]*Account{},
+                                       },
+                               },
+                               t: NewTransaction(
+                                       tranGetClientInfoText, &[]byte{0, 1},
+                                       NewField(fieldUserID, []byte{0, 1}),
+                               ),
+                       },
+                       wantRes: []Transaction{
+                               {
+                                       Flags:     0x00,
+                                       IsReply:   0x01,
+                                       Type:      []byte{0, 0x00},
+                                       ID:        []byte{0, 0, 0, 0},
+                                       ErrorCode: []byte{0, 0, 0, 1},
+                                       Fields: []Field{
+                                               NewField(fieldError, []byte("You are not allowed to get client info.")),
+                                       },
+                               },
+                       },
+                       wantErr: assert.NoError,
+               },
+               {
+                       name: "with a valid user",
+                       args: args{
+                               cc: &ClientConn{
+                                       UserName:   []byte("Testy McTest"),
+                                       RemoteAddr: "1.2.3.4:12345",
+                                       Account: &Account{
+                                               Access: func() *[]byte {
+                                                       var bits accessBitmap
+                                                       bits.Set(accessGetClientInfo)
+                                                       access := bits[:]
+                                                       return &access
+                                               }(),
+                                               Name:  "test",
+                                               Login: "test",
+                                       },
+                                       Server: &Server{
+                                               Accounts: map[string]*Account{},
+                                               Clients: map[uint16]*ClientConn{
+                                                       uint16(1): {
+                                                               UserName:   []byte("Testy McTest"),
+                                                               RemoteAddr: "1.2.3.4:12345",
+                                                               Account: &Account{
+                                                                       Access: func() *[]byte {
+                                                                               var bits accessBitmap
+                                                                               bits.Set(accessGetClientInfo)
+                                                                               access := bits[:]
+                                                                               return &access
+                                                                       }(),
+                                                                       Name:  "test",
+                                                                       Login: "test",
+                                                               },
+                                                       },
+                                               },
+                                       },
+                                       transfers: map[int]map[[4]byte]*FileTransfer{
+                                               FileDownload:   {},
+                                               FileUpload:     {},
+                                               FolderDownload: {},
+                                               FolderUpload:   {},
+                                       },
+                               },
+                               t: NewTransaction(
+                                       tranGetClientInfoText, &[]byte{0, 1},
+                                       NewField(fieldUserID, []byte{0, 1}),
+                               ),
+                       },
+                       wantRes: []Transaction{
+                               {
+                                       Flags:     0x00,
+                                       IsReply:   0x01,
+                                       Type:      []byte{0x1, 0x2f},
+                                       ID:        []byte{0, 0, 0, 0},
+                                       ErrorCode: []byte{0, 0, 0, 0},
+                                       Fields: []Field{
+                                               NewField(fieldData, []byte(
+                                                       strings.Replace(`Nickname:   Testy McTest
+Name:       test
+Account:    test
+Address:    1.2.3.4:12345
+
+-------- File Downloads ---------
+
+None.
+
+------- Folder Downloads --------
+
+None.
+
+--------- File Uploads ----------
+
+None.
+
+-------- Folder Uploads ---------
+
+None.
+
+------- Waiting Downloads -------
+
+None.
+
+`, "\n", "\r", -1)),
+                                               ),
+                                               NewField(fieldUserName, []byte("Testy McTest")),
+                                       },
+                               },
+                       },
+                       wantErr: assert.NoError,
+               },
+       }
+       for _, tt := range tests {
+               t.Run(tt.name, func(t *testing.T) {
+                       gotRes, err := HandleGetClientInfoText(tt.args.cc, tt.args.t)
+                       if !tt.wantErr(t, err, fmt.Sprintf("HandleGetClientInfoText(%v, %v)", tt.args.cc, tt.args.t)) {
+                               return
+                       }
+                       tranAssertEqual(t, tt.wantRes, gotRes)
+               })
+       }
+}
index 9d2a97f732afc80bcad3997f6f88023d0256c114..4c80581511bbd1276a7bcf5cd0ef309519b9de89 100644 (file)
@@ -1,7 +1,6 @@
 package hotline
 
 import (
 package hotline
 
 import (
-       "bufio"
        "bytes"
        "encoding/binary"
        "errors"
        "bytes"
        "encoding/binary"
        "errors"
@@ -31,9 +30,7 @@ func (tf *transfer) Write(b []byte) (int, error) {
        return len(b), nil
 }
 
        return len(b), nil
 }
 
-const fileCopyBufSize = 4096
-
-func receiveFile(r io.Reader, targetFile, resForkFile, infoFork io.Writer) error {
+func receiveFile(r io.Reader, targetFile, resForkFile, infoFork, counterWriter io.Writer) error {
        var ffo flattenedFileObject
        if _, err := ffo.ReadFrom(r); err != nil {
                return err
        var ffo flattenedFileObject
        if _, err := ffo.ReadFrom(r); err != nil {
                return err
@@ -45,12 +42,7 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork io.Writer) error
                return err
        }
 
                return err
        }
 
-       // read and write the data fork
-       bw := bufio.NewWriterSize(targetFile, fileCopyBufSize)
-       if _, err = io.CopyN(bw, r, ffo.dataSize()); err != nil {
-               return err
-       }
-       if err := bw.Flush(); err != nil {
+       if _, err = io.Copy(targetFile, io.TeeReader(r, counterWriter)); err != nil {
                return err
        }
 
                return err
        }
 
@@ -59,47 +51,13 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork io.Writer) error
                        return err
                }
 
                        return err
                }
 
-               bw = bufio.NewWriterSize(resForkFile, fileCopyBufSize)
-               _, err = io.CopyN(resForkFile, r, ffo.rsrcSize())
-               if err != nil {
-                       return err
-               }
-               if err := bw.Flush(); err != nil {
+               if _, err = io.Copy(resForkFile, io.TeeReader(r, counterWriter)); err != nil {
                        return err
                }
        }
        return nil
 }
 
                        return err
                }
        }
        return nil
 }
 
-func sendFile(w io.Writer, r io.Reader, offset int) (err error) {
-       br := bufio.NewReader(r)
-       if _, err := br.Discard(offset); err != nil {
-               return err
-       }
-
-       rSendBuffer := make([]byte, 1024)
-       for {
-               var bytesRead int
-
-               if bytesRead, err = br.Read(rSendBuffer); err == io.EOF {
-                       if _, err := w.Write(rSendBuffer[:bytesRead]); err != nil {
-                               return err
-                       }
-                       return nil
-               }
-               if err != nil {
-                       return err
-               }
-               // totalSent += int64(bytesRead)
-
-               // fileTransfer.BytesSent += bytesRead
-
-               if _, err := w.Write(rSendBuffer[:bytesRead]); err != nil {
-                       return err
-               }
-       }
-}
-
 func (s *Server) bannerDownload(w io.Writer) error {
        bannerBytes, err := os.ReadFile(filepath.Join(s.ConfigDir, s.Config.BannerFile))
        if err != nil {
 func (s *Server) bannerDownload(w io.Writer) error {
        bannerBytes, err := os.ReadFile(filepath.Join(s.ConfigDir, s.Config.BannerFile))
        if err != nil {
index fb7d39cfa89da66614d2db0797c994dc852ce335..fb3e0da11b8628e538a305f79d27dbf28760fd34 100644 (file)
@@ -188,7 +188,7 @@ func Test_receiveFile(t *testing.T) {
                        targetFile := &bytes.Buffer{}
                        resForkFile := &bytes.Buffer{}
                        infoForkFile := &bytes.Buffer{}
                        targetFile := &bytes.Buffer{}
                        resForkFile := &bytes.Buffer{}
                        infoForkFile := &bytes.Buffer{}
-                       err := receiveFile(tt.args.conn, targetFile, resForkFile, infoForkFile)
+                       err := receiveFile(tt.args.conn, targetFile, resForkFile, infoForkFile, io.Discard)
                        if !tt.wantErr(t, err, fmt.Sprintf("receiveFile(%v, %v, %v)", tt.args.conn, targetFile, resForkFile)) {
                                return
                        }
                        if !tt.wantErr(t, err, fmt.Sprintf("receiveFile(%v, %v, %v)", tt.args.conn, targetFile, resForkFile)) {
                                return
                        }