diff options
| author | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-11-28 00:39:17 +0100 |
|---|---|---|
| committer | Ruben Beltran del Rio <git@r.bdr.sh> | 2025-11-28 00:39:17 +0100 |
| commit | 9f67542d8469db45c823e347b1868b3582d9e5a7 (patch) | |
| tree | 88741b3d8633758e4f6f5cbc292f338bc99602a0 /hotline/file_transfer.go | |
| parent | 8f9edf2f3bb18f7ab1a04ead182a1daf2cfd41d9 (diff) | |
| parent | 8ddb9bb228389b198a76d6df21de005da4fad66b (diff) | |
Diffstat (limited to 'hotline/file_transfer.go')
| -rw-r--r-- | hotline/file_transfer.go | 121 |
1 files changed, 68 insertions, 53 deletions
diff --git a/hotline/file_transfer.go b/hotline/file_transfer.go index 4e71bd6..386b24a 100644 --- a/hotline/file_transfer.go +++ b/hotline/file_transfer.go @@ -2,6 +2,7 @@ package hotline import ( "bufio" + "bytes" "crypto/rand" "encoding/binary" "errors" @@ -11,6 +12,7 @@ import ( "log/slog" "math" "os" + "path" "path/filepath" "slices" "strings" @@ -170,37 +172,44 @@ type folderUpload struct { FileNamePath []byte } -//func (fu *folderUpload) Write(p []byte) (int, error) { -// if len(p) < 7 { -// return 0, errors.New("buflen too short") -// } -// copy(fu.DataSize[:], p[0:2]) -// copy(fu.IsFolder[:], p[2:4]) -// copy(fu.PathItemCount[:], p[4:6]) -// -// fu.FileNamePath = make([]byte, binary.BigEndian.Uint16(fu.DataSize[:])-4) // -4 to subtract the path separator bytes TODO: wat -// n, err := io.ReadFull(rwc, fu.FileNamePath) -// if err != nil { -// return 0, err -// } -// -// return n + 6, nil -//} +// pathSegmentScanner implements bufio.SplitFunc for parsing path segments +func pathSegmentScanner(data []byte, _ bool) (advance int, token []byte, err error) { + if len(data) < 3 { + return 0, nil, nil + } + + segLen := int(data[2]) + totalLen := 3 + segLen + + if len(data) < totalLen { + return 0, nil, nil + } + + return totalLen, data[0:totalLen], nil +} func (fu *folderUpload) FormattedPath() string { pathItemLen := binary.BigEndian.Uint16(fu.PathItemCount[:]) + if pathItemLen == 0 { + return "" + } + var pathSegments []string - pathData := fu.FileNamePath + scanner := bufio.NewScanner(bytes.NewReader(fu.FileNamePath)) + scanner.Split(pathSegmentScanner) - // TODO: implement scanner interface instead? - for i := uint16(0); i < pathItemLen; i++ { - segLen := pathData[2] - pathSegments = append(pathSegments, string(pathData[3:3+segLen])) - pathData = pathData[3+segLen:] + for scanner.Scan() && len(pathSegments) < int(pathItemLen) { + segmentData := scanner.Bytes() + if len(segmentData) >= 3 { + segLen := int(segmentData[2]) + if len(segmentData) >= 3+segLen { + pathSegments = append(pathSegments, string(segmentData[3:3+segLen])) + } + } } - return filepath.Join(pathSegments...) + return path.Join(pathSegments...) } type FileHeader struct { @@ -243,18 +252,12 @@ func (fh *FileHeader) Read(p []byte) (int, error) { } func DownloadHandler(w io.Writer, fullPath string, fileTransfer *FileTransfer, fs FileStore, rLogger *slog.Logger, preserveForks bool) error { - //s.Stats.DownloadCounter += 1 - //s.Stats.DownloadsInProgress += 1 - //defer func() { - // s.Stats.DownloadsInProgress -= 1 - //}() - var dataOffset int64 if fileTransfer.FileResumeData != nil { dataOffset = int64(binary.BigEndian.Uint32(fileTransfer.FileResumeData.ForkInfoList[0].DataSize[:])) } - fw, err := NewFileWrapper(fs, fullPath, 0) + hlFile, err := NewFile(fs, fullPath, 0) if err != nil { return fmt.Errorf("reading file header: %v", err) } @@ -264,12 +267,12 @@ func DownloadHandler(w io.Writer, fullPath string, fileTransfer *FileTransfer, f // If file transfer options are included, that means this is a "quick preview" request. In this case skip sending // the flat file info and proceed directly to sending the file data. if fileTransfer.Options == nil { - if _, err = io.Copy(w, fw.Ffo); err != nil { + if _, err = io.Copy(w, hlFile.Ffo); err != nil { return fmt.Errorf("send flat file object: %v", err) } } - file, err := fw.dataForkReader() + file, err := hlFile.dataForkReader() if err != nil { return fmt.Errorf("open data fork reader: %v", err) } @@ -285,13 +288,13 @@ func DownloadHandler(w io.Writer, fullPath string, fileTransfer *FileTransfer, f // If the client requested to resume transfer, do not send the resource fork header. if fileTransfer.FileResumeData == nil { - err = binary.Write(w, binary.BigEndian, fw.rsrcForkHeader()) + err = binary.Write(w, binary.BigEndian, hlFile.rsrcForkHeader()) if err != nil { return fmt.Errorf("send resource fork header: %v", err) } } - rFile, _ := fw.rsrcForkFile() + rFile, _ := hlFile.rsrcForkFile() //if err != nil { // // return fmt.Errorf("open resource fork file: %v", err) //} @@ -318,17 +321,18 @@ func UploadHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileTransfe if err == nil { // return fmt.Errorf("existing file found: %s", fullPath) } - // if errors.Is(err, fs.ErrNotExist) { - // If not found, open or create a new .incomplete file - file, err = os.OpenFile(fullPath+IncompleteFileSuffix, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) - if err != nil { - return err - } - // } - defer file.Close() + if !errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("check file existence: %w", err) + } + + // If not found, open or create a new .incomplete file + file, err = os.OpenFile(fullPath+IncompleteFileSuffix, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return fmt.Errorf("open temp file for uploade: %w", err) + } - f, err := NewFileWrapper(fileStore, fullPath, 0) + f, err := NewFile(fileStore, fullPath, 0) if err != nil { return err } @@ -350,9 +354,16 @@ func UploadHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileTransfe } if err := receiveFile(rwc, file, rForkWriter, iForkWriter, fileTransfer.bytesSentCounter); err != nil { + _ = file.Close() // Close on error return fmt.Errorf("receive file: %v", err) } + // Close the file before attempting to rename it. + if err := file.Close(); err != nil { + return fmt.Errorf("close file: %v", err) + } + + // Rename the temporary upload file to the final file name. if err := fileStore.Rename(fullPath+".incomplete", fullPath); err != nil { return fmt.Errorf("rename incomplete file: %v", err) } @@ -413,7 +424,7 @@ func DownloadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *Fil return nil } - hlFile, err := NewFileWrapper(fileStore, path, 0) + hlFile, err := NewFile(fileStore, path, 0) if err != nil { return err } @@ -486,7 +497,6 @@ func DownloadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *Fil return fmt.Errorf("error opening file: %w", err) } - // wr := bufio.NewWriterSize(rwc, 1460) if _, err = io.Copy(rwc, io.TeeReader(file, fileTransfer.bytesSentCounter)); err != nil { return fmt.Errorf("error sending file: %w", err) } @@ -542,7 +552,6 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT //s.Stats.UploadCounter += 1 var fu folderUpload - // TODO: implement io.Writer on folderUpload and replace this if _, err := io.ReadFull(rwc, fu.DataSize[:]); err != nil { return err } @@ -552,14 +561,14 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT if _, err := io.ReadFull(rwc, fu.PathItemCount[:]); err != nil { return err } - fu.FileNamePath = make([]byte, binary.BigEndian.Uint16(fu.DataSize[:])-4) // -4 to subtract the path separator bytes TODO: wat + fu.FileNamePath = make([]byte, binary.BigEndian.Uint16(fu.DataSize[:])-4) // -4 to subtract the length of the DataSize and IsFolder fields if _, err := io.ReadFull(rwc, fu.FileNamePath); err != nil { return err } if fu.IsFolder == [2]byte{0, 1} { - if _, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath())); os.IsNotExist(err) { - if err := os.Mkdir(filepath.Join(fullPath, fu.FormattedPath()), 0777); err != nil { + if _, err := os.Stat(path.Join(fullPath, fu.FormattedPath())); os.IsNotExist(err) { + if err := os.Mkdir(path.Join(fullPath, fu.FormattedPath()), 0777); err != nil { return err } } @@ -572,7 +581,7 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT nextAction := DlFldrActionSendFile // Check if we have the full file already. If so, send dlFldrAction_NextFile to client to skip. - _, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath())) + _, err := os.Stat(path.Join(fullPath, fu.FormattedPath())) if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } @@ -581,7 +590,7 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT } // Check if we have a partial file already. If so, send dlFldrAction_ResumeFile to client to resume upload. - incompleteFile, err := os.Stat(filepath.Join(fullPath, fu.FormattedPath()+IncompleteFileSuffix)) + incompleteFile, err := os.Stat(path.Join(fullPath, fu.FormattedPath()+IncompleteFileSuffix)) if err != nil && !errors.Is(err, fs.ErrNotExist) { return err } @@ -634,9 +643,9 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT return err } - filePath := filepath.Join(fullPath, fu.FormattedPath()) + filePath := path.Join(fullPath, fu.FormattedPath()) - hlFile, err := NewFileWrapper(fileStore, filePath, 0) + hlFile, err := NewFile(fileStore, filePath, 0) if err != nil { return err } @@ -665,6 +674,12 @@ func UploadFolderHandler(rwc io.ReadWriter, fullPath string, fileTransfer *FileT return err } + // Close the file before attempting to rename it. + if err := incWriter.Close(); err != nil { + return fmt.Errorf("close file: %v", err) + } + + // Rename the temporary upload file to the final file name. if err := os.Rename(filePath+".incomplete", filePath); err != nil { return err } |