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
return os.Remove(name)
}
+func (fs *OSFileStore) Create(name string) (*os.File, error) {
+ return os.Create(name)
+}
+
type MockFileStore struct {
mock.Mock
}
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)
+}
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
// 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 {
// 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?
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))
return transferSize
}
-func (ffif FlatFileInformationFork) ReadNameSize() []byte {
+func (ffif *FlatFileInformationFork) ReadNameSize() []byte {
size := make([]byte, 2)
binary.BigEndian.PutUint16(size, uint16(len(ffif.Name)))
}
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[:]...)
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
}
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
}
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
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,
},
})
}
}
-
-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()")
- })
- }
-}
}
go func() {
- if err := s.TransferFile(conn); err != nil {
+ if err := s.handleFileTransfer(conn); err != nil {
s.Logger.Errorw("file transfer error", "reason", err)
}
}()
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)
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 {
}
}
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
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)
}
// 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
s.Logger.Infow("File download started",
"fileName", info.Name(),
"transactionRef", fileTransfer.ReferenceNumber,
- "RemoteAddr", conn.RemoteAddr().String(),
"TransferSize", fmt.Sprintf("%x", ffo.TransferSize()),
)
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
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:
s.Logger.Infow(
"Folder upload started",
"transactionRef", fileTransfer.ReferenceNumber,
- "RemoteAddr", conn.RemoteAddr().String(),
"dstPath", dstPath,
"TransferSize", fmt.Sprintf("%x", fileTransfer.TransferSize),
"FolderItemCount", fileTransfer.FolderItemCount,
}
}
- 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
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)
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[:]),
}
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 {
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")
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) {
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
}
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
"bytes"
"encoding/binary"
"errors"
+ "io"
)
type transfer struct {
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
+}