diff --git a/src/client/attachment.go b/src/client/attachment.go index 9121d89..a51d902 100644 --- a/src/client/attachment.go +++ b/src/client/attachment.go @@ -1,23 +1,33 @@ package client import ( + "encoding/base64" + "errors" + "os" "reflect" "strings" + + "github.com/gabriel-vasile/mimetype" + uuid "github.com/gofrs/uuid" ) type AttachmentEntry struct { - MimeInfo string - FileName string - Base64 string - FilePath string + MimeInfo string + FileName string + DirName string + Base64 string + FilePath string + attachmentTmpDir string } -func NewAttachmentEntry(attachmentData string) *AttachmentEntry { +func NewAttachmentEntry(attachmentData string, attachmentTmpDir string) *AttachmentEntry { attachmentEntry := AttachmentEntry{ - MimeInfo: "", - FileName: "", - Base64: "", - FilePath: "", + MimeInfo: "", + FileName: "", + DirName: "", + Base64: "", + FilePath: "", + attachmentTmpDir: attachmentTmpDir, } attachmentEntry.extractMetaData(attachmentData) @@ -52,6 +62,70 @@ func (attachmentEntry *AttachmentEntry) extractMetaData(attachmentData string) { } } +func (attachmentEntry *AttachmentEntry) storeBase64AsTemporaryFile() error { + if strings.Compare(attachmentEntry.Base64, "") == 0 { + return errors.New("The base64 data does not exist.") + } + + dec, err := base64.StdEncoding.DecodeString(attachmentEntry.Base64) + if err != nil { + return err + } + + // if no custom filename + if strings.Compare(attachmentEntry.FileName, "") == 0 { + mimeType := mimetype.Detect(dec) + + fileNameUuid, err := uuid.NewV4() + if err != nil { + return err + } + attachmentEntry.FileName = fileNameUuid.String() + mimeType.Extension() + } + + dirNameUuid, err := uuid.NewV4() + if err != nil { + return err + } + + attachmentEntry.DirName = dirNameUuid.String() + dirPath := attachmentEntry.attachmentTmpDir + attachmentEntry.DirName + if err := os.Mkdir(dirPath, os.ModePerm); err != nil { + return err + } + + attachmentEntry.FilePath = dirPath + string(os.PathSeparator) + attachmentEntry.FileName + + f, err := os.Create(attachmentEntry.FilePath) + if err != nil { + return err + } + defer f.Close() + + if _, err := f.Write(dec); err != nil { + attachmentEntry.cleanUp() + return err + } + if err := f.Sync(); err != nil { + attachmentEntry.cleanUp() + return err + } + f.Close() + + return nil +} + +func (attachmentEntry *AttachmentEntry) cleanUp() { + if strings.Compare(attachmentEntry.FilePath, "") != 0 { + os.Remove(attachmentEntry.FilePath) + } + + if strings.Compare(attachmentEntry.DirName, "") != 0 { + dirPath := attachmentEntry.attachmentTmpDir + attachmentEntry.DirName + os.Remove(dirPath) + } +} + func (attachmentEntry *AttachmentEntry) setFieldValueByName(fieldName string, fieldValue string) { reflectPointer := reflect.ValueOf(attachmentEntry) reflectStructure := reflectPointer.Elem() diff --git a/src/client/attachment_test.go b/src/client/attachment_test.go index a81d257..8e4e711 100644 --- a/src/client/attachment_test.go +++ b/src/client/attachment_test.go @@ -1,6 +1,8 @@ package client import ( + "flag" + "os" "strings" "testing" ) @@ -11,42 +13,45 @@ func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *tes inputData string resultIsWithMetaData bool base64Expected string + base64Valid bool fileNameExpected string mimeInfoExpected string toDataForSignal string }{ { - "BC base64 - compatibility", "MTIzNDU=", false, "MTIzNDU=", "", "", "MTIzNDU=", + "BC base64 - compatibility", "MTIzNDU=", false, "MTIzNDU=", true, "", "", "MTIzNDU=", }, { - "+base64 -data -filename", "base64,MTIzNDU=", false, "base64,MTIzNDU=", "", "", "base64,MTIzNDU=", + "+base64 -data -filename", "base64,MTIzNDU=", false, "base64,MTIzNDU=", false, "", "", "base64,MTIzNDU=", }, { - "+base64 +data -filename", "data:someData;base64,MTIzNDU=", true, "MTIzNDU=", "", "someData", "data:someData;base64,MTIzNDU=", + "+base64 +data -filename", "data:someData;base64,MTIzNDU=", true, "MTIzNDU=", true, "", "someData", "data:someData;base64,MTIzNDU=", }, { - "+base64 -data +filename", "filename=file.name;base64,MTIzNDU=", false, "filename=file.name;base64,MTIzNDU=", "", "", "filename=file.name;base64,MTIzNDU=", + "+base64 -data +filename", "filename=file.name;base64,MTIzNDU=", false, "filename=file.name;base64,MTIzNDU=", false, "", "", "filename=file.name;base64,MTIzNDU=", }, { - "+base64 +data +filename", "data:someData;filename=file.name;base64,MTIzNDU=", true, "MTIzNDU=", "file.name", "someData", "data:someData;filename=file.name;base64,MTIzNDU=", + "+base64 +data +filename", "data:someData;filename=file.name;base64,MTIzNDU=", true, "MTIzNDU=", true, "file.name", "someData", "data:someData;filename=file.name;base64,MTIzNDU=", }, { - "-base64 -data -filename", "INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", "", "", "INVALIDMTIzNDU=", + "-base64 -data -filename", "INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", false, "", "", "INVALIDMTIzNDU=", }, { - "-base64 +data -filename", "data:someData;INVALIDMTIzNDU=", false, "data:someData;INVALIDMTIzNDU=", "", "", "data:someData;INVALIDMTIzNDU=", + "-base64 +data -filename", "data:someData;INVALIDMTIzNDU=", false, "data:someData;INVALIDMTIzNDU=", false, "", "", "data:someData;INVALIDMTIzNDU=", }, { - "-base64 -data +filename", "filename=file.name;INVALIDMTIzNDU=", false, "filename=file.name;INVALIDMTIzNDU=", "", "", "filename=file.name;INVALIDMTIzNDU=", + "-base64 -data +filename", "filename=file.name;INVALIDMTIzNDU=", false, "filename=file.name;INVALIDMTIzNDU=", false, "", "", "filename=file.name;INVALIDMTIzNDU=", }, { - "-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=", + "-base64 +data +filename", "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "data:someData;filename=file.name;INVALIDMTIzNDU=", false, "", "", "data:someData;filename=file.name;INVALIDMTIzNDU=", }, } + attachmentTmp := flag.String("attachment-tmp-dir", string(os.PathSeparator)+"tmp"+string(os.PathSeparator), "Attachment tmp directory") + for _, tt := range testCases { t.Run(tt.nameTest, func(t *testing.T) { - attachmentEntry := NewAttachmentEntry(tt.inputData) + attachmentEntry := NewAttachmentEntry(tt.inputData, *attachmentTmp) if attachmentEntry.isWithMetaData() != tt.resultIsWithMetaData { t.Errorf("isWithMetaData() got \"%v\", want \"%v\"", attachmentEntry.isWithMetaData(), tt.resultIsWithMetaData) @@ -67,6 +72,36 @@ func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *tes if strings.Compare(attachmentEntry.toDataForSignal(), tt.toDataForSignal) != 0 { t.Errorf("toDataForSignal() got \"%v\", want \"%v\"", attachmentEntry.toDataForSignal(), tt.toDataForSignal) } + + err := attachmentEntry.storeBase64AsTemporaryFile() + if err != nil && tt.base64Valid { + t.Error("storeBase64AsTemporaryFile error: %w", err) + return + } + + info, err := os.Stat(attachmentEntry.FilePath) + if os.IsNotExist(err) && tt.base64Valid { + t.Error("file not exists after storeBase64AsTemporaryFile: %w", err) + return + } + if (info == nil || info.IsDir()) && tt.base64Valid { + t.Error("is not a file by path after storeBase64AsTemporaryFile") + t.Error(attachmentEntry) + return + } + + attachmentEntry.cleanUp() + + info, err = os.Stat(attachmentEntry.FilePath) + if info != nil && !os.IsNotExist(err) && tt.base64Valid { + t.Error("no info or file exists after cleanUp") + return + } + info, err = os.Stat(*attachmentTmp + attachmentEntry.DirName) + if info != nil && !os.IsNotExist(err) && tt.base64Valid { + t.Error("dir exists after cleanUp") + return + } }) } } diff --git a/src/client/client.go b/src/client/client.go index 449dce5..844a8e4 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -12,7 +12,6 @@ import ( "strings" securejoin "github.com/cyphar/filepath-securejoin" - "github.com/gabriel-vasile/mimetype" "github.com/h2non/filetype" uuid "github.com/gofrs/uuid" @@ -136,11 +135,7 @@ func cleanupTmpFiles(paths []string) { func cleanupAttachmentEntries(attachmentEntries []AttachmentEntry) { for _, attachmentEntry := range attachmentEntries { - if len(attachmentEntry.FilePath) == 0 { - continue - } - - os.Remove(attachmentEntry.FilePath) + attachmentEntry.cleanUp() } } @@ -311,43 +306,15 @@ func (s *SignalClient) send(number string, message string, attachmentEntries := []AttachmentEntry{} for _, base64Attachment := range base64Attachments { - attachmentEntry := NewAttachmentEntry(base64Attachment) + attachmentEntry := NewAttachmentEntry(base64Attachment, s.attachmentTmpDir) - u, err := uuid.NewV4() + err := attachmentEntry.storeBase64AsTemporaryFile() if err != nil { + cleanupAttachmentEntries(attachmentEntries) return nil, err } - dec, err := base64.StdEncoding.DecodeString(attachmentEntry.Base64) - if err != nil { - return nil, err - } - - mimeType := mimetype.Detect(dec) - - if attachmentEntry.isWithMetaData() { - attachmentEntries = append(attachmentEntries, *attachmentEntry) - continue - } - attachmentEntry.FilePath = s.attachmentTmpDir + u.String() + mimeType.Extension() attachmentEntries = append(attachmentEntries, *attachmentEntry) - - f, err := os.Create(attachmentEntry.FilePath) - if err != nil { - return nil, err - } - defer f.Close() - - if _, err := f.Write(dec); err != nil { - cleanupAttachmentEntries(attachmentEntries) - return nil, err - } - if err := f.Sync(); err != nil { - cleanupAttachmentEntries(attachmentEntries) - return nil, err - } - - f.Close() } if s.signalCliMode == JsonRpc {