From: Jeff Halter Date: Tue, 31 May 2022 04:37:19 +0000 (-0700) Subject: Fix and refactor file transfers to handle resource forks X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/85767504e4dc622c5ff469733e49c0cebcee57f1?ds=sidebyside;hp=--cc Fix and refactor file transfers to handle resource forks --- 85767504e4dc622c5ff469733e49c0cebcee57f1 diff --git a/hotline/file_store.go b/hotline/file_store.go index e9d083f..ed0ffa4 100644 --- a/hotline/file_store.go +++ b/hotline/file_store.go @@ -13,6 +13,7 @@ type FileStore interface { Open(name string) (*os.File, error) Symlink(oldname, newname string) error Remove(name string) error + Create(name string) (*os.File, error) // TODO: implement these // Rename(oldpath string, newpath string) error // RemoveAll(path string) error @@ -40,6 +41,10 @@ func (fs *OSFileStore) Remove(name string) error { return os.Remove(name) } +func (fs *OSFileStore) Create(name string) (*os.File, error) { + return os.Create(name) +} + type MockFileStore struct { mock.Mock } @@ -72,3 +77,8 @@ func (mfs *MockFileStore) Remove(name string) error { args := mfs.Called(name) return args.Error(0) } + +func (mfs *MockFileStore) Create(name string) (*os.File, error) { + args := mfs.Called(name) + return args.Get(0).(*os.File), args.Error(1) +} diff --git a/hotline/flattened_file_object.go b/hotline/flattened_file_object.go index fae8ab8..ba6b063 100644 --- a/hotline/flattened_file_object.go +++ b/hotline/flattened_file_object.go @@ -18,7 +18,7 @@ type FlatFileHeader struct { Format [4]byte // Always "FILP" Version [2]byte // Always 1 RSVD [16]byte // Always empty zeros - ForkCount [2]byte // Always 2 + ForkCount [2]byte // Number of forks } // NewFlatFileHeader returns a FlatFileHeader struct @@ -33,10 +33,10 @@ func NewFlatFileHeader() FlatFileHeader { // FlatFileInformationForkHeader is the second section of a "Flattened File Object" type FlatFileInformationForkHeader struct { - ForkType []byte // Always "INFO" - CompressionType []byte // Always 0; Compression was never implemented in the Hotline protocol - RSVD []byte // Always zeros - DataSize []byte // Size of the flat file information fork + ForkType [4]byte // Always "INFO" + CompressionType [4]byte // Always 0; Compression was never implemented in the Hotline protocol + RSVD [4]byte // Always zeros + DataSize [4]byte // Size of the flat file information fork } type FlatFileInformationFork struct { @@ -74,7 +74,7 @@ func NewFlatFileInformationFork(fileName string, modifyTime []byte) FlatFileInfo // DataSize calculates the size of the flat file information fork, which is // 72 bytes for the fixed length fields plus the length of the Name + Comment -func (ffif FlatFileInformationFork) DataSize() []byte { +func (ffif *FlatFileInformationFork) DataSize() []byte { size := make([]byte, 4) // TODO: Can I do math directly on two byte slices? @@ -85,9 +85,9 @@ func (ffif FlatFileInformationFork) DataSize() []byte { return size } -func (ffo flattenedFileObject) TransferSize() []byte { +func (ffo *flattenedFileObject) TransferSize() []byte { payloadSize := len(ffo.BinaryMarshal()) - dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize) + dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]) transferSize := make([]byte, 4) binary.BigEndian.PutUint32(transferSize, dataSize+uint32(payloadSize)) @@ -95,7 +95,7 @@ func (ffo flattenedFileObject) TransferSize() []byte { return transferSize } -func (ffif FlatFileInformationFork) ReadNameSize() []byte { +func (ffif *FlatFileInformationFork) ReadNameSize() []byte { size := make([]byte, 2) binary.BigEndian.PutUint16(size, uint16(len(ffif.Name))) @@ -103,65 +103,45 @@ func (ffif FlatFileInformationFork) ReadNameSize() []byte { } type FlatFileDataForkHeader struct { - ForkType []byte - CompressionType []byte - RSVD []byte - DataSize []byte + ForkType [4]byte + CompressionType [4]byte + RSVD [4]byte + DataSize [4]byte } -// ReadFlattenedFileObject parses a byte slice into a flattenedFileObject -func ReadFlattenedFileObject(bytes []byte) flattenedFileObject { - nameSize := bytes[110:112] +func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { + + nameSize := b[70:72] bs := binary.BigEndian.Uint16(nameSize) - nameEnd := 112 + bs + nameEnd := 72 + bs - commentSize := bytes[nameEnd : nameEnd+2] + commentSize := b[nameEnd : nameEnd+2] commentLen := binary.BigEndian.Uint16(commentSize) commentStartPos := int(nameEnd) + 2 commentEndPos := int(nameEnd) + 2 + int(commentLen) - comment := bytes[commentStartPos:commentEndPos] - - // dataSizeField := bytes[nameEnd+14+commentLen : nameEnd+18+commentLen] - // dataSize := binary.BigEndian.Uint32(dataSizeField) - - ffo := flattenedFileObject{ - FlatFileHeader: NewFlatFileHeader(), - FlatFileInformationForkHeader: FlatFileInformationForkHeader{ - ForkType: bytes[24:28], - CompressionType: bytes[28:32], - RSVD: bytes[32:36], - DataSize: bytes[36:40], - }, - FlatFileInformationFork: FlatFileInformationFork{ - Platform: bytes[40:44], - TypeSignature: bytes[44:48], - CreatorSignature: bytes[48:52], - Flags: bytes[52:56], - PlatformFlags: bytes[56:60], - RSVD: bytes[60:92], - CreateDate: bytes[92:100], - ModifyDate: bytes[100:108], - NameScript: bytes[108:110], - NameSize: bytes[110:112], - Name: bytes[112:nameEnd], - CommentSize: bytes[nameEnd : nameEnd+2], - Comment: comment, - }, - FlatFileDataForkHeader: FlatFileDataForkHeader{ - ForkType: bytes[commentEndPos : commentEndPos+4], - CompressionType: bytes[commentEndPos+4 : commentEndPos+8], - RSVD: bytes[commentEndPos+8 : commentEndPos+12], - DataSize: bytes[commentEndPos+12 : commentEndPos+16], - }, - } - - return ffo + comment := b[commentStartPos:commentEndPos] + + ffif.Platform = b[0:4] + ffif.TypeSignature = b[4:8] + ffif.CreatorSignature = b[8:12] + ffif.Flags = b[12:16] + ffif.PlatformFlags = b[16:20] + ffif.RSVD = b[20:52] + ffif.CreateDate = b[52:60] + ffif.ModifyDate = b[60:68] + ffif.NameScript = b[68:70] + ffif.NameSize = b[70:72] + ffif.Name = b[72:nameEnd] + ffif.CommentSize = b[nameEnd : nameEnd+2] + ffif.Comment = comment + + return nil } -func (f flattenedFileObject) BinaryMarshal() []byte { +func (f *flattenedFileObject) BinaryMarshal() []byte { var out []byte out = append(out, f.FlatFileHeader.Format[:]...) out = append(out, f.FlatFileHeader.Version[:]...) @@ -187,10 +167,10 @@ func (f flattenedFileObject) BinaryMarshal() []byte { out = append(out, f.FlatFileInformationFork.CommentSize...) out = append(out, f.FlatFileInformationFork.Comment...) - out = append(out, f.FlatFileDataForkHeader.ForkType...) - out = append(out, f.FlatFileDataForkHeader.CompressionType...) - out = append(out, f.FlatFileDataForkHeader.RSVD...) - out = append(out, f.FlatFileDataForkHeader.DataSize...) + out = append(out, f.FlatFileDataForkHeader.ForkType[:]...) + out = append(out, f.FlatFileDataForkHeader.CompressionType[:]...) + out = append(out, f.FlatFileDataForkHeader.RSVD[:]...) + out = append(out, f.FlatFileDataForkHeader.DataSize[:]...) return out } @@ -220,10 +200,10 @@ func NewFlattenedFileObject(fileRoot string, filePath, fileName []byte) (*flatte FlatFileHeader: NewFlatFileHeader(), FlatFileInformationFork: NewFlatFileInformationFork(string(fileName), mTime), FlatFileDataForkHeader: FlatFileDataForkHeader{ - ForkType: []byte("DATA"), - CompressionType: []byte{0, 0, 0, 0}, - RSVD: []byte{0, 0, 0, 0}, - DataSize: dataSize, + ForkType: [4]byte{0x44, 0x41, 0x54, 0x41}, // "DATA" + CompressionType: [4]byte{}, + RSVD: [4]byte{}, + DataSize: [4]byte{dataSize[0], dataSize[1], dataSize[2], dataSize[3]}, }, }, nil } diff --git a/hotline/flattened_file_object_test.go b/hotline/flattened_file_object_test.go index 12b6d32..300eb34 100644 --- a/hotline/flattened_file_object_test.go +++ b/hotline/flattened_file_object_test.go @@ -1,26 +1,12 @@ package hotline import ( - "bytes" - "encoding/hex" "fmt" "github.com/stretchr/testify/assert" "os" "testing" ) -func TestReadFlattenedFileObject(t *testing.T) { - testData, _ := hex.DecodeString("46494c500001000000000000000000000000000000000002494e464f000000000000000000000052414d414354455854747478740000000000000100000000000000000000000000000000000000000000000000000000000000000007700000ba74247307700000ba74247300000008746573742e74787400004441544100000000000000000000000474657374") - - ffo := ReadFlattenedFileObject(testData) - - format := ffo.FlatFileHeader.Format[:] - want := []byte("FILP") - if !bytes.Equal(format, want) { - t.Errorf("ReadFlattenedFileObject() = %q, want %q", format, want) - } -} - func TestNewFlattenedFileObject(t *testing.T) { type args struct { fileRoot string @@ -45,10 +31,10 @@ func TestNewFlattenedFileObject(t *testing.T) { FlatFileInformationForkHeader: FlatFileInformationForkHeader{}, FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt", make([]byte, 8)), FlatFileDataForkHeader: FlatFileDataForkHeader{ - ForkType: []byte("DATA"), - CompressionType: []byte{0, 0, 0, 0}, - RSVD: []byte{0, 0, 0, 0}, - DataSize: []byte{0x00, 0x00, 0x00, 0x17}, + ForkType: [4]byte{0x4d, 0x41, 0x43, 0x52}, // DATA + CompressionType: [4]byte{0, 0, 0, 0}, + RSVD: [4]byte{0, 0, 0, 0}, + DataSize: [4]byte{0x00, 0x00, 0x00, 0x17}, }, FileData: nil, }, @@ -79,67 +65,3 @@ func TestNewFlattenedFileObject(t *testing.T) { }) } } - -func Test_flattenedFileObject_BinaryMarshal(t *testing.T) { - - testData, _ := hex.DecodeString("46494c500001000000000000000000000000000000000002494e464f000000000000000000000052414d414354455854747478740000000000000100000000000000000000000000000000000000000000000000000000000000000007700000ba74247307700000ba74247300000008746573742e74787400004441544100000000000000000000000474657374") - testFile := ReadFlattenedFileObject(testData) - testFile.FlatFileInformationFork.Comment = []byte("test!") - testFile.FlatFileInformationFork.CommentSize = []byte{0x00, 0x05} - - type fields struct { - FlatFileHeader FlatFileHeader - FlatFileInformationForkHeader FlatFileInformationForkHeader - FlatFileInformationFork FlatFileInformationFork - FlatFileDataForkHeader FlatFileDataForkHeader - FileData []byte - } - tests := []struct { - name string - fields fields - want []byte - }{ - { - name: "with a valid file", - fields: fields{ - FlatFileHeader: testFile.FlatFileHeader, - FlatFileInformationForkHeader: testFile.FlatFileInformationForkHeader, - FlatFileInformationFork: testFile.FlatFileInformationFork, - FlatFileDataForkHeader: testFile.FlatFileDataForkHeader, - FileData: testFile.FileData, - }, - want: []byte{ - 0x46, 0x49, 0x4c, 0x50, 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, - 0x49, 0x4e, 0x46, 0x4f, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x57, - 0x41, 0x4d, 0x41, 0x43, 0x54, 0x45, 0x58, 0x54, - 0x74, 0x74, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x70, 0x00, 0x00, - 0xba, 0x74, 0x24, 0x73, 0x07, 0x70, 0x00, 0x00, - 0xba, 0x74, 0x24, 0x73, 0x00, 0x00, 0x00, 0x08, - 0x74, 0x65, 0x73, 0x74, 0x2e, 0x74, 0x78, 0x74, - 0x00, 0x05, 0x74, 0x65, 0x73, 0x74, 0x21, 0x44, - 0x41, 0x54, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - f := flattenedFileObject{ - FlatFileHeader: tt.fields.FlatFileHeader, - FlatFileInformationForkHeader: tt.fields.FlatFileInformationForkHeader, - FlatFileInformationFork: tt.fields.FlatFileInformationFork, - FlatFileDataForkHeader: tt.fields.FlatFileDataForkHeader, - FileData: tt.fields.FileData, - } - assert.Equalf(t, tt.want, f.BinaryMarshal(), "BinaryMarshal()") - }) - } -} diff --git a/hotline/server.go b/hotline/server.go index 7b409d5..90b7ea0 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -91,7 +91,7 @@ func (s *Server) ServeFileTransfers(ln net.Listener) error { } go func() { - if err := s.TransferFile(conn); err != nil { + if err := s.handleFileTransfer(conn); err != nil { s.Logger.Errorw("file transfer error", "reason", err) } }() @@ -613,8 +613,13 @@ const dlFldrActionSendFile = 1 const dlFldrActionResumeFile = 2 const dlFldrActionNextFile = 3 -func (s *Server) TransferFile(conn net.Conn) error { - defer func() { _ = conn.Close() }() +// handleFileTransfer receives a client net.Conn from the file transfer server, performs the requested transfer type, then closes the connection +func (s *Server) handleFileTransfer(conn io.ReadWriteCloser) error { + defer func() { + if err := conn.Close(); err != nil { + s.Logger.Errorw("error closing connection", "error", err) + } + }() txBuf := make([]byte, 16) _, err := conn.Read(txBuf) @@ -647,7 +652,7 @@ func (s *Server) TransferFile(conn net.Conn) error { return err } - s.Logger.Infow("File download started", "filePath", fullFilePath, "transactionRef", fileTransfer.ReferenceNumber, "RemoteAddr", conn.RemoteAddr().String()) + s.Logger.Infow("File download started", "filePath", fullFilePath, "transactionRef", fileTransfer.ReferenceNumber) // Start by sending flat file object to client if _, err := conn.Write(ffo.BinaryMarshal()); err != nil { @@ -675,63 +680,20 @@ func (s *Server) TransferFile(conn net.Conn) error { } } case FileUpload: - const buffSize = 1460 - - uploadBuf := make([]byte, buffSize) - - _, err := conn.Read(uploadBuf) - if err != nil { - return err - } - - ffo := ReadFlattenedFileObject(uploadBuf) - payloadLen := len(ffo.BinaryMarshal()) - fileSize := int(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize)) - destinationFile := s.Config.FileRoot + ReadFilePath(fileTransfer.FilePath) + "/" + string(fileTransfer.FileName) - s.Logger.Infow( - "File upload started", - "transactionRef", fileTransfer.ReferenceNumber, - "RemoteAddr", conn.RemoteAddr().String(), - "size", fileSize, - "dstFile", destinationFile, - ) - - newFile, err := os.Create(destinationFile) + newFile, err := FS.Create(destinationFile) if err != nil { return err } - defer func() { _ = newFile.Close() }() - if _, err := newFile.Write(uploadBuf[payloadLen:]); err != nil { - return err - } - receivedBytes := buffSize - payloadLen - - for { - if (fileSize - receivedBytes) < buffSize { - s.Logger.Infow( - "File upload complete", - "transactionRef", fileTransfer.ReferenceNumber, - "RemoteAddr", conn.RemoteAddr().String(), - "size", fileSize, - "dstFile", destinationFile, - ) - - if _, err := io.CopyN(newFile, conn, int64(fileSize-receivedBytes)); err != nil { - return fmt.Errorf("file transfer failed: %s", err) - } - return nil - } + s.Logger.Infow("File upload started", "transactionRef", fileTransfer.ReferenceNumber, "dstFile", destinationFile) - // Copy N bytes from conn to upload file - n, err := io.CopyN(newFile, conn, buffSize) - if err != nil { - return err - } - receivedBytes += int(n) + if err := receiveFile(conn, newFile, nil); err != nil { + s.Logger.Errorw("file upload error", "error", err) } + + s.Logger.Infow("File upload complete", "transactionRef", fileTransfer.ReferenceNumber, "dstFile", destinationFile) case FolderDownload: // Folder Download flow: // 1. Get filePath from the transfer @@ -767,22 +729,28 @@ func (s *Server) TransferFile(conn net.Conn) error { basePathLen := len(fullFilePath) - readBuffer := make([]byte, 1024) + s.Logger.Infow("Start folder download", "path", fullFilePath, "ReferenceNumber", fileTransfer.ReferenceNumber) - s.Logger.Infow("Start folder download", "path", fullFilePath, "ReferenceNumber", fileTransfer.ReferenceNumber, "RemoteAddr", conn.RemoteAddr()) + nextAction := make([]byte, 2) + if _, err := conn.Read(nextAction); err != nil { + return err + } i := 0 - _ = filepath.Walk(fullFilePath+"/", func(path string, info os.FileInfo, _ error) error { + err = filepath.Walk(fullFilePath+"/", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } i += 1 - subPath := path[basePathLen:] + subPath := path[basePathLen+1:] s.Logger.Infow("Sending fileheader", "i", i, "path", path, "fullFilePath", fullFilePath, "subPath", subPath, "IsDir", info.IsDir()) - fileHeader := NewFileHeader(subPath, info.IsDir()) - if i == 1 { return nil } + fileHeader := NewFileHeader(subPath, info.IsDir()) + // Send the file header to client if _, err := conn.Write(fileHeader.Payload()); err != nil { s.Logger.Errorf("error sending file header: %v", err) @@ -790,12 +758,14 @@ func (s *Server) TransferFile(conn net.Conn) error { } // Read the client's Next Action request - // TODO: Remove hardcoded behavior and switch behaviors based on the next action send - if _, err := conn.Read(readBuffer); err != nil { + if _, err := conn.Read(nextAction); err != nil { return err } + if nextAction[1] == 3 { + return nil + } - s.Logger.Infow("Client folder download action", "action", fmt.Sprintf("%X", readBuffer[0:2])) + s.Logger.Infow("Client folder download action", "action", fmt.Sprintf("%X", nextAction[0:2])) if info.IsDir() { return nil @@ -814,7 +784,6 @@ func (s *Server) TransferFile(conn net.Conn) error { s.Logger.Infow("File download started", "fileName", info.Name(), "transactionRef", fileTransfer.ReferenceNumber, - "RemoteAddr", conn.RemoteAddr().String(), "TransferSize", fmt.Sprintf("%x", ffo.TransferSize()), ) @@ -824,7 +793,7 @@ func (s *Server) TransferFile(conn net.Conn) error { return err } - // Send file bytes to client + // Send ffo bytes to client if _, err := conn.Write(ffo.BinaryMarshal()); err != nil { s.Logger.Error(err) return err @@ -835,28 +804,22 @@ func (s *Server) TransferFile(conn net.Conn) error { return err } - sendBuffer := make([]byte, 1048576) - totalBytesSent := len(ffo.BinaryMarshal()) + // Copy N bytes from file to connection + _, err = io.CopyN(conn, file, int64(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]))) + if err != nil { + return err + } + file.Close() - for { - bytesRead, err := file.Read(sendBuffer) - if err == io.EOF { - // Read the client's Next Action request - // TODO: Remove hardcoded behavior and switch behaviors based on the next action send - if _, err := conn.Read(readBuffer); err != nil { - s.Logger.Errorf("error reading next action: %v", err) - return err - } - break - } + // TODO: optionally send resource fork header and resource fork data - sentBytes, readErr := conn.Write(sendBuffer[:bytesRead]) - totalBytesSent += sentBytes - if readErr != nil { - return err - } + // Read the client's Next Action request + if _, err := conn.Read(nextAction); err != nil { + return err } - return nil + // TODO: switch behavior based on possible next action + + return err }) case FolderUpload: @@ -867,7 +830,6 @@ func (s *Server) TransferFile(conn net.Conn) error { s.Logger.Infow( "Folder upload started", "transactionRef", fileTransfer.ReferenceNumber, - "RemoteAddr", conn.RemoteAddr().String(), "dstPath", dstPath, "TransferSize", fmt.Sprintf("%x", fileTransfer.TransferSize), "FolderItemCount", fileTransfer.FolderItemCount, @@ -881,8 +843,6 @@ func (s *Server) TransferFile(conn net.Conn) error { } } - readBuffer := make([]byte, 1024) - // Begin the folder upload flow by sending the "next file action" to client if _, err := conn.Write([]byte{0, dlFldrActionNextFile}); err != nil { return err @@ -891,8 +851,10 @@ func (s *Server) TransferFile(conn net.Conn) error { fileSize := make([]byte, 4) itemCount := binary.BigEndian.Uint16(fileTransfer.FolderItemCount) + readBuffer := make([]byte, 1024) for i := uint16(0); i < itemCount; i++ { - if _, err := conn.Read(readBuffer); err != nil { + _, err := conn.Read(readBuffer) + if err != nil { return err } fu := readFolderUpload(readBuffer) @@ -900,7 +862,6 @@ func (s *Server) TransferFile(conn net.Conn) error { s.Logger.Infow( "Folder upload continued", "transactionRef", fmt.Sprintf("%x", fileTransfer.ReferenceNumber), - "RemoteAddr", conn.RemoteAddr().String(), "FormattedPath", fu.FormattedPath(), "IsFolder", fmt.Sprintf("%x", fu.IsFolder), "PathItemCount", binary.BigEndian.Uint16(fu.PathItemCount[:]), @@ -928,14 +889,21 @@ func (s *Server) TransferFile(conn net.Conn) error { } if _, err := conn.Read(fileSize); err != nil { - fmt.Println("Error reading:", err.Error()) // TODO: handle + return err } - s.Logger.Infow("Starting file transfer", "fileNum", i+1, "totalFiles", itemCount, "fileSize", fileSize) + filePath := dstPath + "/" + fu.FormattedPath() + s.Logger.Infow("Starting file transfer", "path", filePath, "fileNum", i+1, "totalFiles", itemCount, "fileSize", binary.BigEndian.Uint32(fileSize)) - if err := transferFile(conn, dstPath+"/"+fu.FormattedPath()); err != nil { + newFile, err := FS.Create(filePath) + if err != nil { + return err + } + + if err := receiveFile(conn, newFile, ioutil.Discard); err != nil { s.Logger.Error(err) } + _ = newFile.Close() // Tell client to send next file if _, err := conn.Write([]byte{0, dlFldrActionNextFile}); err != nil { @@ -943,11 +911,6 @@ func (s *Server) TransferFile(conn net.Conn) error { return err } - // Client sends "MACR" after the file. Read and discard. - // TODO: This doesn't seem to be documented. What is this? Maybe resource fork? - if _, err := conn.Read(readBuffer); err != nil { - return err - } } } s.Logger.Infof("Folder upload complete") @@ -956,43 +919,6 @@ func (s *Server) TransferFile(conn net.Conn) error { return nil } -func transferFile(conn net.Conn, dst string) error { - const buffSize = 1024 - buf := make([]byte, buffSize) - - // Read first chunk of bytes from conn; this will be the Flat File Object and initial chunk of file bytes - if _, err := conn.Read(buf); err != nil { - return err - } - ffo := ReadFlattenedFileObject(buf) - payloadLen := len(ffo.BinaryMarshal()) - fileSize := int(binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize)) - - newFile, err := os.Create(dst) - if err != nil { - return err - } - defer func() { _ = newFile.Close() }() - if _, err := newFile.Write(buf[payloadLen:]); err != nil { - return err - } - receivedBytes := buffSize - payloadLen - - for { - if (fileSize - receivedBytes) < buffSize { - _, err := io.CopyN(newFile, conn, int64(fileSize-receivedBytes)) - return err - } - - // Copy N bytes from conn to upload file - n, err := io.CopyN(newFile, conn, buffSize) - if err != nil { - return err - } - receivedBytes += int(n) - } -} - // sortedClients is a utility function that takes a map of *ClientConn and returns a sorted slice of the values. // The purpose of this is to ensure that the ordering of client connections is deterministic so that test assertions work. func sortedClients(unsortedClients map[uint16]*ClientConn) (clients []*ClientConn) { diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index c17510a..992dcec 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -401,7 +401,7 @@ func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e NewField(fieldFileType, ffo.FlatFileInformationFork.TypeSignature), NewField(fieldFileCreateDate, ffo.FlatFileInformationFork.CreateDate), NewField(fieldFileModifyDate, ffo.FlatFileInformationFork.ModifyDate), - NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize), + NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize[:]), )) return res, err } @@ -1213,7 +1213,7 @@ func HandleDownloadFile(cc *ClientConn, t *Transaction) (res []Transaction, err NewField(fieldRefNum, transactionRef), NewField(fieldWaitingCount, []byte{0x00, 0x00}), // TODO: Implement waiting count NewField(fieldTransferSize, ffo.TransferSize()), - NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize), + NewField(fieldFileSize, ffo.FlatFileDataForkHeader.DataSize[:]), )) return res, err diff --git a/hotline/transfer.go b/hotline/transfer.go index fa431ce..703dae0 100644 --- a/hotline/transfer.go +++ b/hotline/transfer.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/binary" "errors" + "io" ) type transfer struct { @@ -26,3 +27,78 @@ func (tf *transfer) Write(b []byte) (int, error) { return len(b), nil } + +func receiveFile(conn io.Reader, targetFile io.Writer, resForkFile io.Writer) error { + ffhBuf := make([]byte, 24) + if _, err := conn.Read(ffhBuf); err != nil { + return err + } + + var ffh FlatFileHeader + err := binary.Read(bytes.NewReader(ffhBuf), binary.BigEndian, &ffh) + if err != nil { + return err + } + + ffifhBuf := make([]byte, 16) + if _, err := conn.Read(ffifhBuf); err != nil { + return err + } + + var ffifh FlatFileInformationForkHeader + err = binary.Read(bytes.NewReader(ffifhBuf), binary.BigEndian, &ffifh) + if err != nil { + return err + } + + var ffif FlatFileInformationFork + + dataLen := binary.BigEndian.Uint32(ffifh.DataSize[:]) + ffifBuf := make([]byte, dataLen) + if _, err := conn.Read(ffifBuf); err != nil { + return err + } + if err := ffif.UnmarshalBinary(ffifBuf); err != nil { + return err + } + + var ffdfh FlatFileDataForkHeader + ffdfhBuf := make([]byte, 16) + if _, err := conn.Read(ffdfhBuf); err != nil { + return err + } + err = binary.Read(bytes.NewReader(ffdfhBuf), binary.BigEndian, &ffdfh) + if err != nil { + return err + } + + // this will be zero if the file only has a resource fork + fileSize := int(binary.BigEndian.Uint32(ffdfh.DataSize[:])) + + // Copy N bytes from conn to upload file + _, err = io.CopyN(targetFile, conn, int64(fileSize)) + if err != nil { + return err + } + + if ffh.ForkCount == [2]byte{0, 3} { + var resForkHeader FlatFileDataForkHeader + resForkBuf := make([]byte, 16) + + if _, err := conn.Read(resForkBuf); err != nil { + return err + } + err = binary.Read(bytes.NewReader(resForkBuf), binary.BigEndian, &resForkHeader) + if err != nil { + return err + } + + fileSize = int(binary.BigEndian.Uint32(resForkHeader.DataSize[:])) + + _, err = io.CopyN(resForkFile, conn, int64(fileSize)) + if err != nil { + return err + } + } + return nil +}