From 14fd65c1e761084118c04a76592549ab309f2b06 Mon Sep 17 00:00:00 2001 From: zeetabit Date: Thu, 28 Jul 2022 00:02:37 +0200 Subject: [PATCH 1/5] Support attachment base64 and custom filename. --- src/client/client.go | 51 ++++++++++++++++++++++++++++--------------- src/docs/docs.go | 6 +++-- src/docs/swagger.json | 6 +++-- src/docs/swagger.yaml | 2 ++ 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/client/client.go b/src/client/client.go index 96db759..0615df5 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -4,12 +4,12 @@ import ( "encoding/base64" "encoding/json" "errors" + "fmt" "io/ioutil" "os" "path/filepath" "strconv" "strings" - "fmt" securejoin "github.com/cyphar/filepath-securejoin" "github.com/gabriel-vasile/mimetype" @@ -134,6 +134,16 @@ func cleanupTmpFiles(paths []string) { } } +func cleanupAttachmentEntries(attachmentEntries []AttachmentEntry) { + for _, attachmentEntry := range attachmentEntries { + if len(attachmentEntry.FilePath) == 0 { + continue + } + + os.Remove(attachmentEntry.FilePath) + } +} + func convertInternalGroupIdToGroupId(internalId string) string { return groupPrefix + base64.StdEncoding.EncodeToString([]byte(internalId)) } @@ -194,7 +204,6 @@ func getContainerId() (string, error) { return containerId, nil } - func ConvertGroupIdToInternalGroupId(id string) (string, error) { groupIdWithoutPrefix := strings.TrimPrefix(id, groupPrefix) @@ -300,35 +309,41 @@ func (s *SignalClient) send(number string, message string, groupId = string(grpId) } - attachmentTmpPaths := []string{} + attachmentEntries := []AttachmentEntry{} for _, base64Attachment := range base64Attachments { + attachmentEntry := NewAttachmentEntry(base64Attachment) + u, err := uuid.NewV4() if err != nil { return nil, err } - dec, err := base64.StdEncoding.DecodeString(base64Attachment) + dec, err := base64.StdEncoding.DecodeString(attachmentEntry.Base64) if err != nil { return nil, err } mimeType := mimetype.Detect(dec) - attachmentTmpPath := s.attachmentTmpDir + u.String() + mimeType.Extension() - attachmentTmpPaths = append(attachmentTmpPaths, attachmentTmpPath) + if attachmentEntry.isWithMetaData() { + attachmentEntries = append(attachmentEntries, *attachmentEntry) + continue + } + attachmentEntry.FilePath = s.attachmentTmpDir + u.String() + mimeType.Extension() + attachmentEntries = append(attachmentEntries, *attachmentEntry) - f, err := os.Create(attachmentTmpPath) + f, err := os.Create(attachmentEntry.FilePath) if err != nil { return nil, err } defer f.Close() if _, err := f.Write(dec); err != nil { - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) return nil, err } if err := f.Sync(); err != nil { - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) return nil, err } @@ -354,13 +369,13 @@ func (s *SignalClient) send(number string, message string, } else { request.Recipients = recipients } - if len(attachmentTmpPaths) > 0 { - request.Attachments = append(request.Attachments, attachmentTmpPaths...) + for _, attachmentEntry := range attachmentEntries { + request.Attachments = append(request.Attachments, attachmentEntry.toDataForSignal()) } rawData, err := jsonRpc2Client.getRaw("send", request) if err != nil { - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) return nil, err } @@ -379,14 +394,16 @@ func (s *SignalClient) send(number string, message string, cmd = append(cmd, []string{"-g", groupId}...) } - if len(attachmentTmpPaths) > 0 { + if len(attachmentEntries) > 0 { cmd = append(cmd, "-a") - cmd = append(cmd, attachmentTmpPaths...) + for _, attachmentEntry := range attachmentEntries { + cmd = append(cmd, attachmentEntry.toDataForSignal()) + } } rawData, err := s.cliClient.Execute(true, cmd, message) if err != nil { - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) if strings.Contains(err.Error(), signalCliV2GroupError) { return nil, errors.New("Cannot send message to group - please first update your profile.") } @@ -394,12 +411,12 @@ func (s *SignalClient) send(number string, message string, } resp.Timestamp, err = strconv.ParseInt(strings.TrimSuffix(rawData, "\n"), 10, 64) if err != nil { - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) return nil, err } } - cleanupTmpFiles(attachmentTmpPaths) + cleanupAttachmentEntries(attachmentEntries) return &resp, nil } diff --git a/src/docs/docs.go b/src/docs/docs.go index 2b67e29..6bb1c69 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -1686,7 +1686,8 @@ var doc = `{ "type": "object", "properties": { "base64_attachment": { - "type": "string" + "type": "string", + "example": " or 'data:;base64,' or 'data:;filename=;base64,'" }, "is_group": { "type": "boolean" @@ -1711,7 +1712,8 @@ var doc = `{ "base64_attachments": { "type": "array", "items": { - "type": "string" + "type": "string", + "example": " or 'data:;base64,' or 'data:;filename=;base64,'" } }, "message": { diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 1befde4..90ae731 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -1670,7 +1670,8 @@ "type": "object", "properties": { "base64_attachment": { - "type": "string" + "type": "string", + "example": " or 'data:;base64,' or 'data:;filename=;base64,'" }, "is_group": { "type": "boolean" @@ -1695,7 +1696,8 @@ "base64_attachments": { "type": "array", "items": { - "type": "string" + "type": "string", + "example": " or 'data:;base64,' or 'data:;filename=;base64,'" } }, "message": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 67739a2..18efe84 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -107,6 +107,7 @@ definitions: properties: base64_attachment: type: string + example: " or 'data:;base64,' or 'data:;filename=;base64,'" is_group: type: boolean message: @@ -123,6 +124,7 @@ definitions: base64_attachments: items: type: string + example: " or 'data:;base64,' or 'data:;filename=;base64,'" type: array message: type: string From c52fa0e53c371afd0bfef81106999b0a3599dfa9 Mon Sep 17 00:00:00 2001 From: zeetabit Date: Thu, 28 Jul 2022 00:50:02 +0200 Subject: [PATCH 2/5] Support attachment base64 and custom filename. --- src/client/attachment.go | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/client/attachment.go diff --git a/src/client/attachment.go b/src/client/attachment.go new file mode 100644 index 0000000..d33c260 --- /dev/null +++ b/src/client/attachment.go @@ -0,0 +1,92 @@ +package client + +import ( + "reflect" + "strings" +) + +type AttachmentEntry struct { + MimeInfo string + FileName string + Base64 string + FilePath string +} + +func NewAttachmentEntry(attachmentData string) *AttachmentEntry { + attachmentEntry := AttachmentEntry{ + MimeInfo: "", + FileName: "", + Base64: "", + FilePath: "", + } + + attachmentEntry.extractMetaData(attachmentData) + + return &attachmentEntry +} + +func (attachmentEntry *AttachmentEntry) extractMetaData(attachmentData string) { + base64FlagIndex := strings.LastIndex(attachmentData, "base64,") + + if !strings.Contains(attachmentData, "data:") && base64FlagIndex == -1 { + attachmentEntry.Base64 = attachmentData + return + } + + attachmentEntry.Base64 = attachmentData[base64FlagIndex+len("base64,"):] + metaDataKeys := map[string]string{ + "data:": "MimeInfo", + "filename=": "FileName", + } + + for _, metaDataLineItem := range strings.Split(attachmentData[:base64FlagIndex-1], ";") { + for metaDataKey, metaDataFieldName := range metaDataKeys { + flagIndex := strings.Index(metaDataLineItem, metaDataKey) + + if flagIndex != 0 { + continue + } + + attachmentEntry.setFieldValueByName(metaDataFieldName, metaDataLineItem[len(metaDataKey):]) + } + } +} + +func (attachmentEntry *AttachmentEntry) setFieldValueByName(fieldName string, fieldValue string) { + reflectPointer := reflect.ValueOf(attachmentEntry) + reflectStructure := reflectPointer.Elem() + + if reflectStructure.Kind() != reflect.Struct { + return + } + + field := reflectStructure.FieldByName(fieldName) + if !field.IsValid() { + return + } + + if !field.CanSet() || field.Kind() != reflect.String { + return + } + + field.SetString(fieldValue) +} + +func (attachmentEntry *AttachmentEntry) isWithMetaData() bool { + return len(attachmentEntry.MimeInfo) > 0 && len(attachmentEntry.Base64) > 0 +} + +func (attachmentEntry *AttachmentEntry) toDataForSignal() string { + result := "" + if !attachmentEntry.isWithMetaData() && len(attachmentEntry.FilePath) > 0 { + return attachmentEntry.FilePath + } + + result = "data:" + attachmentEntry.MimeInfo + + if len(attachmentEntry.FileName) > 0 { + result = result + ";filename=" + attachmentEntry.FileName + } + + return result + ";base64," + attachmentEntry.Base64 +} From 270278ca301e13cef1f9a4363a4edf5dac64c36c Mon Sep 17 00:00:00 2001 From: zeetabit Date: Sun, 31 Jul 2022 13:45:29 +0200 Subject: [PATCH 3/5] Bugfix for support attachment base64 and custom filename. Introduce tests for attachment data parsing. Use tests at build stage. --- Dockerfile | 2 +- src/api/api.go | 6 +-- src/client/attachment.go | 11 ++++-- src/client/attachment_test.go | 72 +++++++++++++++++++++++++++++++++++ src/client/client.go | 2 +- src/docs/docs.go | 36 +++++++++++++----- src/docs/swagger.json | 36 +++++++++++++----- src/docs/swagger.yaml | 47 ++++++++++++++++------- 8 files changed, 169 insertions(+), 43 deletions(-) create mode 100644 src/client/attachment_test.go diff --git a/Dockerfile b/Dockerfile index fa8a2f2..b05b0c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -118,7 +118,7 @@ COPY src/go.mod /tmp/signal-cli-rest-api-src/ COPY src/go.sum /tmp/signal-cli-rest-api-src/ # build signal-cli-rest-api -RUN cd /tmp/signal-cli-rest-api-src && swag init && go build +RUN cd /tmp/signal-cli-rest-api-src && swag init && go test ./client -v && go build # build supervisorctl_config_creator RUN cd /tmp/signal-cli-rest-api-src/scripts && go build -o jsonrpc2-helper diff --git a/src/api/api.go b/src/api/api.go index a7be4f8..58c3a44 100644 --- a/src/api/api.go +++ b/src/api/api.go @@ -69,7 +69,7 @@ type RegisterNumberRequest struct { } type UnregisterNumberRequest struct { - DeleteAccount bool `json:"delete_account" example:"false"` + DeleteAccount bool `json:"delete_account" example:"false"` DeleteLocalData bool `json:"delete_local_data" example:"false"` } @@ -88,7 +88,7 @@ type SendMessageV1 struct { Number string `json:"number"` Recipients []string `json:"recipients"` Message string `json:"message"` - Base64Attachment string `json:"base64_attachment"` + Base64Attachment string `json:"base64_attachment" example:"'' OR 'data:;base64,' OR 'data:;filename=;base64,'"` IsGroup bool `json:"is_group"` } @@ -96,7 +96,7 @@ type SendMessageV2 struct { Number string `json:"number"` Recipients []string `json:"recipients"` Message string `json:"message"` - Base64Attachments []string `json:"base64_attachments"` + Base64Attachments []string `json:"base64_attachments" example:",data:;base64,data:;filename=;base64"` } type TypingIndicatorRequest struct { diff --git a/src/client/attachment.go b/src/client/attachment.go index d33c260..9121d89 100644 --- a/src/client/attachment.go +++ b/src/client/attachment.go @@ -28,7 +28,7 @@ func NewAttachmentEntry(attachmentData string) *AttachmentEntry { func (attachmentEntry *AttachmentEntry) extractMetaData(attachmentData string) { base64FlagIndex := strings.LastIndex(attachmentData, "base64,") - if !strings.Contains(attachmentData, "data:") && base64FlagIndex == -1 { + if !strings.Contains(attachmentData, "data:") || base64FlagIndex == -1 { attachmentEntry.Base64 = attachmentData return } @@ -77,12 +77,15 @@ func (attachmentEntry *AttachmentEntry) isWithMetaData() bool { } func (attachmentEntry *AttachmentEntry) toDataForSignal() string { - result := "" - if !attachmentEntry.isWithMetaData() && len(attachmentEntry.FilePath) > 0 { + if len(attachmentEntry.FilePath) > 0 { return attachmentEntry.FilePath } - result = "data:" + attachmentEntry.MimeInfo + if !attachmentEntry.isWithMetaData() { + return attachmentEntry.Base64 + } + + result := "data:" + attachmentEntry.MimeInfo if len(attachmentEntry.FileName) > 0 { result = result + ";filename=" + attachmentEntry.FileName diff --git a/src/client/attachment_test.go b/src/client/attachment_test.go new file mode 100644 index 0000000..8385dde --- /dev/null +++ b/src/client/attachment_test.go @@ -0,0 +1,72 @@ +package client + +import ( + "strings" + "testing" +) + +func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *testing.T) { + testCases := []struct { + nameTest string + inputData string + resultIsWithMetaData bool + base64Expected string + fileNameExpected string + mimeInfoExpected string + toDataForSignal string + }{ + { + "BC base64 - compatibility", "MTIzNDU=", false, "MTIzNDU=", "", "", "MTIzNDU=", + }, + { + "+base64 -data -filename", "base64,MTIzNDU=", false, "base64,MTIzNDU=", "", "", "base64,MTIzNDU=", + }, + { + "+base64 +data -filename", "data:someData;base64,MTIzNDU=", true, "MTIzNDU=", "", "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", "data:someData;filename=file.name;base64,MTIzNDU=", true, "MTIzNDU=", "file.name", "someData", "data:someData;filename=file.name;base64,MTIzNDU=", + }, + { + "-base64 -data -filename", "INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", "", "", "INVALIDMTIzNDU=", + }, + { + "-base64 +data -filename", "data:someData;INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", "", "", "data:someData;INVALIDMTIzNDU=", + }, + { + "-base64 -data +filename", "filename=file.name;INVALIDMTIzNDU=", false, "filename=file.name;INVALIDMTIzNDU=", "", "", "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=", + }, + } + + for _, tt := range testCases { + t.Run(tt.nameTest, func(t *testing.T) { + attachmentEntry := NewAttachmentEntry(tt.inputData) + + if attachmentEntry.isWithMetaData() != tt.resultIsWithMetaData { + t.Errorf("isWithMetaData() got \"%v\", want \"%v\"", attachmentEntry.isWithMetaData(), tt.resultIsWithMetaData) + } + + if strings.Compare(attachmentEntry.Base64, tt.base64Expected) != 0 { + t.Errorf("Base64 got \"%v\", want \"%v\"", attachmentEntry.Base64, tt.base64Expected) + } + + if strings.Compare(attachmentEntry.FileName, tt.fileNameExpected) != 0 { + t.Errorf("FileName got \"%v\", want \"%v\"", attachmentEntry.FileName, tt.fileNameExpected) + } + + if strings.Compare(attachmentEntry.MimeInfo, tt.mimeInfoExpected) != 0 { + t.Errorf("MimeInfo got \"%v\", want \"%v\"", attachmentEntry.MimeInfo, tt.mimeInfoExpected) + } + + if strings.Compare(attachmentEntry.toDataForSignal(), tt.toDataForSignal) != 0 { + t.Errorf("toDataForSignal() got \"%v\", want \"%v\"", attachmentEntry.toDataForSignal(), tt.toDataForSignal) + } + }) + } +} diff --git a/src/client/client.go b/src/client/client.go index 0615df5..449dce5 100644 --- a/src/client/client.go +++ b/src/client/client.go @@ -461,7 +461,7 @@ func (s *SignalClient) UnregisterNumber(number string, deleteAccount bool, delet command := []string{"--config", s.signalCliConfig, "-a", number, "deleteLocalAccountData"} _, err2 := s.cliClient.Execute(true, command, "") if (err2 != nil) && (err != nil) { - err = fmt.Errorf("%w (%w)", err, err2) + err = fmt.Errorf("%w (%s)", err, err2.Error()) } else if (err2 != nil) && (err == nil) { err = err2 } diff --git a/src/docs/docs.go b/src/docs/docs.go index 6bb1c69..926e2bb 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -241,7 +241,9 @@ var doc = `{ } ], "responses": { - "200": {}, + "200": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -281,7 +283,9 @@ var doc = `{ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -323,7 +327,9 @@ var doc = `{ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -365,7 +371,9 @@ var doc = `{ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1200,7 +1208,9 @@ var doc = `{ } ], "responses": { - "201": {}, + "201": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1472,7 +1482,9 @@ var doc = `{ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1687,7 +1699,7 @@ var doc = `{ "properties": { "base64_attachment": { "type": "string", - "example": " or 'data:;base64,' or 'data:;filename=;base64,'" + "example": "'\u003cBASE64 ENCODED DATA\u003e' OR 'data:\u003cMIME-TYPE\u003e;base64,\u003cBASE64 ENCODED DATA\u003e' OR 'data:\u003cMIME-TYPE\u003e;filename=\u003cFILENAME\u003e;base64,\u003cBASE64 ENCODED DATA\u003e'" }, "is_group": { "type": "boolean" @@ -1712,9 +1724,13 @@ var doc = `{ "base64_attachments": { "type": "array", "items": { - "type": "string", - "example": " or 'data:;base64,' or 'data:;filename=;base64,'" - } + "type": "string" + }, + "example": [ + "\u003cBASE64 ENCODED DATA\u003e", + "data:\u003cMIME-TYPE\u003e;base64\u003ccomma\u003e\u003cBASE64 ENCODED DATA\u003e", + "data:\u003cMIME-TYPE\u003e;filename=\u003cFILENAME\u003e;base64\u003ccomma\u003e\u003cBASE64 ENCODED DATA\u003e" + ] }, "message": { "type": "string" diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 90ae731..5d59150 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -225,7 +225,9 @@ } ], "responses": { - "200": {}, + "200": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -265,7 +267,9 @@ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -307,7 +311,9 @@ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -349,7 +355,9 @@ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1184,7 +1192,9 @@ } ], "responses": { - "201": {}, + "201": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1456,7 +1466,9 @@ } ], "responses": { - "204": {}, + "204": { + "description": "" + }, "400": { "description": "Bad Request", "schema": { @@ -1671,7 +1683,7 @@ "properties": { "base64_attachment": { "type": "string", - "example": " or 'data:;base64,' or 'data:;filename=;base64,'" + "example": "'\u003cBASE64 ENCODED DATA\u003e' OR 'data:\u003cMIME-TYPE\u003e;base64,\u003cBASE64 ENCODED DATA\u003e' OR 'data:\u003cMIME-TYPE\u003e;filename=\u003cFILENAME\u003e;base64,\u003cBASE64 ENCODED DATA\u003e'" }, "is_group": { "type": "boolean" @@ -1696,9 +1708,13 @@ "base64_attachments": { "type": "array", "items": { - "type": "string", - "example": " or 'data:;base64,' or 'data:;filename=;base64,'" - } + "type": "string" + }, + "example": [ + "\u003cBASE64 ENCODED DATA\u003e", + "data:\u003cMIME-TYPE\u003e;base64\u003ccomma\u003e\u003cBASE64 ENCODED DATA\u003e", + "data:\u003cMIME-TYPE\u003e;filename=\u003cFILENAME\u003e;base64\u003ccomma\u003e\u003cBASE64 ENCODED DATA\u003e" + ] }, "message": { "type": "string" diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 18efe84..4e710bb 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -106,8 +106,10 @@ definitions: api.SendMessageV1: properties: base64_attachment: + example: ''''' OR ''data:;base64,'' OR ''data:;filename=;base64,''' type: string - example: " or 'data:;base64,' or 'data:;filename=;base64,'" is_group: type: boolean message: @@ -122,9 +124,12 @@ definitions: api.SendMessageV2: properties: base64_attachments: + example: + - + - data:;base64 + - data:;filename=;base64 items: type: string - example: " or 'data:;base64,' or 'data:;filename=;base64,'" type: array message: type: string @@ -391,7 +396,8 @@ paths: produces: - application/json responses: - "200": {} + "200": + description: "" "400": description: Bad Request schema: @@ -418,7 +424,8 @@ paths: produces: - application/json responses: - "204": {} + "204": + description: "" "400": description: Bad Request schema: @@ -446,19 +453,22 @@ paths: produces: - application/json responses: - "204": {} + "204": + description: "" "400": description: Bad Request schema: $ref: '#/definitions/api.Error' - summary: Updates the info associated to a number on the contact list. If the contact doesn’t exist yet, it will be added. + summary: Updates the info associated to a number on the contact list. If the + contact doesn’t exist yet, it will be added. tags: - Contacts /v1/devices/{number}: post: consumes: - application/json - description: Links another device to this device. Only works, if this is the master device. + description: Links another device to this device. Only works, if this is the + master device. parameters: - description: Registered Phone Number in: path @@ -474,7 +484,8 @@ paths: produces: - application/json responses: - "204": {} + "204": + description: "" "400": description: Bad Request schema: @@ -846,7 +857,8 @@ paths: - Identities /v1/identities/{number}/trust/{numberToTrust}: put: - description: Trust an identity. When 'trust_all_known_keys' is set to' true', all known keys of this user are trusted. **This is only recommended for testing.** + description: Trust an identity. When 'trust_all_known_keys' is set to' true', + all known keys of this user are trusted. **This is only recommended for testing.** parameters: - description: Input Data in: body @@ -981,7 +993,9 @@ paths: get: consumes: - application/json - description: Receives Signal Messages from the Signal Network. If you are running the docker container in normal/native mode, this is a GET endpoint. In json-rpc mode this is a websocket endpoint. + description: Receives Signal Messages from the Signal Network. If you are running + the docker container in normal/native mode, this is a GET endpoint. In json-rpc + mode this is a websocket endpoint. parameters: - description: Registered Phone Number in: path @@ -1027,7 +1041,8 @@ paths: produces: - application/json responses: - "201": {} + "201": + description: "" "400": description: Bad Request schema: @@ -1074,7 +1089,8 @@ paths: get: consumes: - application/json - description: Check if one or more phone numbers are registered with the Signal Service. + description: Check if one or more phone numbers are registered with the Signal + Service. parameters: - collectionFormat: multi description: Numbers to check @@ -1192,7 +1208,9 @@ paths: post: consumes: - application/json - description: Disables push support for this device. **WARNING:** If *delete_account* is set to *true*, the account will be deleted from the Signal Server. This cannot be undone without loss. + description: Disables push support for this device. **WARNING:** If *delete_account* + is set to *true*, the account will be deleted from the Signal Server. This + cannot be undone without loss. parameters: - description: Registered Phone Number in: path @@ -1207,7 +1225,8 @@ paths: produces: - application/json responses: - "204": {} + "204": + description: "" "400": description: Bad Request schema: From 34e7db3c6779b1925e4d8dc47031bba52054db43 Mon Sep 17 00:00:00 2001 From: zeetabit Date: Sun, 31 Jul 2022 13:49:27 +0200 Subject: [PATCH 4/5] Bugfix for support attachment base64 and custom filename. Introduce tests for attachment data parsing. Use tests at build stage. --- src/client/attachment_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/attachment_test.go b/src/client/attachment_test.go index 8385dde..a81d257 100644 --- a/src/client/attachment_test.go +++ b/src/client/attachment_test.go @@ -34,7 +34,7 @@ func Test_Attachment_ExtractMetadata_ShouldPrepareDataFor_toDataForSignal(t *tes "-base64 -data -filename", "INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", "", "", "INVALIDMTIzNDU=", }, { - "-base64 +data -filename", "data:someData;INVALIDMTIzNDU=", false, "INVALIDMTIzNDU=", "", "", "data:someData;INVALIDMTIzNDU=", + "-base64 +data -filename", "data:someData;INVALIDMTIzNDU=", false, "data:someData;INVALIDMTIzNDU=", "", "", "data:someData;INVALIDMTIzNDU=", }, { "-base64 -data +filename", "filename=file.name;INVALIDMTIzNDU=", false, "filename=file.name;INVALIDMTIzNDU=", "", "", "filename=file.name;INVALIDMTIzNDU=", From 98a130dd0584ab33135feeb33c34755ef727b0f4 Mon Sep 17 00:00:00 2001 From: zeetabit Date: Thu, 4 Aug 2022 10:35:50 +0200 Subject: [PATCH 5/5] Store base64 files locally before sending. --- src/client/attachment.go | 92 +++++++++++++++++++++++++++++++---- src/client/attachment_test.go | 55 +++++++++++++++++---- src/client/client.go | 41 ++-------------- 3 files changed, 132 insertions(+), 56 deletions(-) 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 {