aboutsummaryrefslogtreecommitdiff
path: root/hotline/file_transfer.go
diff options
context:
space:
mode:
Diffstat (limited to 'hotline/file_transfer.go')
-rw-r--r--hotline/file_transfer.go121
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
}