From 8fe7d8c312589f69b78e5d3d8cb6d7ae590b29c9 Mon Sep 17 00:00:00 2001 From: Mateusz Dahlke <39696234+Xavrax@users.noreply.github.com> Date: Mon, 6 Feb 2023 12:34:53 +0100 Subject: [PATCH 1/3] adding xavrax to code-owners (#143) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f60f0707..e9dfdd4d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,3 @@ -* @kleewho @parfeon @mateusz-dahlke +* @kleewho @parfeon @Xavrax .github/* @parfeon @kleewho README.md @techwritermat @kazydek From aabd5a928beb71a8ce96a52021280745aa5bba84 Mon Sep 17 00:00:00 2001 From: Lukasz Klich Date: Thu, 9 Feb 2023 13:13:56 +0100 Subject: [PATCH 2/3] Add message type and space id in files --- files_send_file.go | 24 +- listener_manager.go | 2 + publish_file_message.go | 22 ++ publish_file_message_test.go | 34 ++ publish_request.go | 1 - subscription_manager.go | 6 +- tests/e2e/file_upload_test_output.txt | Bin 21904 -> 21879 bytes tests/e2e/files_test.go | 482 ++------------------------ 8 files changed, 106 insertions(+), 465 deletions(-) diff --git a/files_send_file.go b/files_send_file.go index 8f08cc75..df0abf7d 100644 --- a/files_send_file.go +++ b/files_send_file.go @@ -48,6 +48,18 @@ func (b *sendFileBuilder) Meta(meta interface{}) *sendFileBuilder { return b } +func (b *sendFileBuilder) SpaceId(id SpaceId) *sendFileBuilder { + b.opts.SpaceId = id + + return b +} + +func (b *sendFileBuilder) MessageType(messageType MessageType) *sendFileBuilder { + b.opts.MessageType = messageType + + return b +} + // ShouldStore if true the messages are stored in History func (b *sendFileBuilder) ShouldStore(store bool) *sendFileBuilder { b.opts.ShouldStore = store @@ -117,6 +129,8 @@ type sendFileOpts struct { CipherKey string TTL int Meta interface{} + SpaceId SpaceId + MessageType MessageType ShouldStore bool QueryParam map[string]string @@ -236,7 +250,15 @@ func newPNSendFileResponse(jsonBytes []byte, o *sendFileOpts, maxCount := o.config().FileMessagePublishRetryLimit for !sent && tryCount < maxCount { tryCount++ - pubFileMessageResponse, pubFileResponseStatus, errPubFileResponse := o.pubnub.PublishFileMessage().TTL(o.TTL).Meta(o.Meta).ShouldStore(o.ShouldStore).Channel(o.Channel).Message(message).Execute() + pubFileMessageResponse, pubFileResponseStatus, errPubFileResponse := o.pubnub.PublishFileMessage(). + TTL(o.TTL). + Meta(o.Meta). + ShouldStore(o.ShouldStore). + Channel(o.Channel). + Message(message). + MessageType(o.MessageType). + SpaceId(o.SpaceId). + Execute() if errPubFileResponse != nil { if tryCount >= maxCount { pubFileResponseStatus.AdditionalData = file diff --git a/listener_manager.go b/listener_manager.go index fc04d202..63c36440 100644 --- a/listener_manager.go +++ b/listener_manager.go @@ -362,4 +362,6 @@ type PNFilesEvent struct { Subscription string Publisher string Timetoken int64 + MessageType MessageType + SpaceId SpaceId } diff --git a/publish_file_message.go b/publish_file_message.go index eb4695ce..24a116f7 100644 --- a/publish_file_message.go +++ b/publish_file_message.go @@ -51,6 +51,18 @@ func (b *publishFileMessageBuilder) Meta(meta interface{}) *publishFileMessageBu return b } +func (b *publishFileMessageBuilder) SpaceId(id SpaceId) *publishFileMessageBuilder { + b.opts.SpaceId = id + + return b +} + +func (b *publishFileMessageBuilder) MessageType(messageType MessageType) *publishFileMessageBuilder { + b.opts.MessageType = messageType + + return b +} + // ShouldStore if true the messages are stored in History func (b *publishFileMessageBuilder) ShouldStore(store bool) *publishFileMessageBuilder { b.opts.ShouldStore = store @@ -129,6 +141,8 @@ type publishFileMessageOpts struct { UsePost bool TTL int Meta interface{} + SpaceId SpaceId + MessageType MessageType ShouldStore bool setTTL bool setShouldStore bool @@ -244,6 +258,14 @@ func (o *publishFileMessageOpts) buildPath() (string, error) { func (o *publishFileMessageOpts) buildQuery() (*url.Values, error) { q := defaultQuery(o.pubnub.Config.UUID, o.pubnub.telemetryManager) + if o.MessageType != "" { + q.Set(publishMessageTypeQueryParam, utils.URLEncode(string(o.MessageType))) + } + + if o.SpaceId != "" { + q.Set(publishSpaceIdQueryParam, utils.URLEncode(string(o.SpaceId))) + } + SetQueryParam(q, o.QueryParam) return q, nil diff --git a/publish_file_message_test.go b/publish_file_message_test.go index 04274f5e..2c9410ba 100644 --- a/publish_file_message_test.go +++ b/publish_file_message_test.go @@ -298,3 +298,37 @@ func TestPublishFileMessageResponseValuePass(t *testing.T) { assert.Nil(err) } + +func TestPublishFileMessageSpaceIdQueryParamIsPassed(t *testing.T) { + a := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + expectedSpaceId := SpaceId("spaceId") + queryValues, _ := pn.PublishFileMessage().SpaceId(expectedSpaceId).opts.buildQuery() + + a.Equal(expectedSpaceId, SpaceId(queryValues.Get(publishSpaceIdQueryParam))) +} + +func TestPublishFileMessageMissingSpaceIdQueryParamIsNotSet(t *testing.T) { + a := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + queryValues, _ := pn.PublishFileMessage().opts.buildQuery() + + a.Equal("", queryValues.Get(publishSpaceIdQueryParam)) +} + +func TestPublishFileMessageMessageTypeQueryParamIsPassed(t *testing.T) { + a := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + expectedMessageType := MessageType("customMessageType") + queryValues, _ := pn.PublishFileMessage().MessageType(expectedMessageType).opts.buildQuery() + + a.Equal(expectedMessageType, MessageType(queryValues.Get(publishMessageTypeQueryParam))) +} + +func TestPublishFileMessageMissingMessageTypeQueryParamIsNotSet(t *testing.T) { + a := assert.New(t) + pn := NewPubNub(NewDemoConfig()) + queryValues, _ := pn.PublishFileMessage().opts.buildQuery() + + a.Equal("", queryValues.Get(publishMessageTypeQueryParam)) +} diff --git a/publish_request.go b/publish_request.go index d44215b6..aa48bb24 100644 --- a/publish_request.go +++ b/publish_request.go @@ -134,7 +134,6 @@ func (b *publishBuilder) Meta(meta interface{}) *publishBuilder { func (b *publishBuilder) SpaceId(id SpaceId) *publishBuilder { b.opts.SpaceId = id - return b } diff --git a/subscription_manager.go b/subscription_manager.go index 6f2ca18b..3d0c4f9b 100644 --- a/subscription_manager.go +++ b/subscription_manager.go @@ -684,7 +684,7 @@ func processNonPresencePayload(m *SubscriptionManager, payload subscribeMessage, } - pnFilesEvent := createPNFilesEvent(messagePayload, m, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken) + pnFilesEvent := createPNFilesEvent(messagePayload, m, actualCh, subscribedCh, channel, subscriptionMatch, payload.IssuingClientID, payload.UserMetadata, timetoken, messageType, payload.SpaceId) m.pubnub.Config.Log.Println("PNMessageTypeFile:", PNMessageTypeFile) m.listenerManager.announceFile(pnFilesEvent) default: @@ -725,7 +725,7 @@ func processSubscribePayload(m *SubscriptionManager, payload subscribeMessage) { } } -func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64) *PNFilesEvent { +func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualCh, subscribedCh, channel, subscriptionMatch, issuingClientID string, userMetadata interface{}, timetoken int64, messageType MessageType, spaceId SpaceId) *PNFilesEvent { var filesPayload map[string]interface{} var ok bool if filesPayload, ok = filePayload.(map[string]interface{}); !ok { @@ -756,6 +756,8 @@ func createPNFilesEvent(filePayload interface{}, m *SubscriptionManager, actualC Timetoken: timetoken, Publisher: issuingClientID, UserMetadata: userMetadata, + SpaceId: spaceId, + MessageType: messageType, } return pnFilesEvent } diff --git a/tests/e2e/file_upload_test_output.txt b/tests/e2e/file_upload_test_output.txt index 6e61f95832057ab82f2a6a32b2455d776b5df647..226f4a064a1e4eced5be9835d0c6c131b4aa3d6a 100644 GIT binary patch literal 21879 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 blocklen || padlen == 0 { - return nil, fmt.Errorf("padding is invalid") - } - // check padding - pad := data[len(data)-padlen:] - for i := 0; i < padlen; i++ { - if pad[i] != byte(padlen) { - return nil, fmt.Errorf("padding is invalid") - } - } - - return data[:len(data)-padlen], nil -} - -func FileEncryptDecryptWithoutBase64Test(t *testing.T) { - assert := assert.New(t) - filepathInput := "file_upload_test_enc.txt" - //filepathInput = "whoami_out.txt" - // filepathInput = "file_dec_3642342.png" - filepathInput = "tux.jpg" - // filepathInput = "gopher.jpg" - filepathInput = "Moth.jpg" - // filepathInput = "file_upload_test.txt" - filepathOutput := "file_upload_test_enc_out.txt" - out, _ := os.Create(filepathOutput) - file, err := os.Open(filepathInput) - key := utils.EncryptCipherKey("enigma") - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - iv := make([]byte, aes.BlockSize) - - if _, err := rand.Read(iv); err != nil { - panic(err) - } - // IV that was breaking TEST - // iv = []byte{73, 187, 52, 211, 20, 183, 129, 64, 119, 12, 190, 93, 7, 15, 70, 7} - iv = []byte{133, 126, 158, 123, 43, 95, 96, 90, 215, 178, 17, 73, 166, 130, 79, 156} - fmt.Println("iv:=>>>", iv) - n, e := out.Write(iv) - fmt.Println(n) - if e != nil { - panic(e) - } - - blockSize := 16 - bufferSize := 16 - p := make([]byte, bufferSize) - mode := cipher.NewCBCEncrypter(block, iv) - cryptoRan := false - fii, _ := file.Stat() - contentLenIn := fii.Size() - var contentRead int64 - - for { - n2, err2 := io.ReadFull(file, p) - contentRead += int64(n2) - if err2 != nil { - fmt.Println("\nerr2:", err2) - if err2 == io.EOF { - ciphertext := make([]byte, blockSize) - copy(ciphertext[:n2], p[:n2]) - fmt.Println("Encrypt EOF EOF") - fmt.Println("ct2 EOF ", ciphertext, string(ciphertext), p[:n2], p) - out.Close() - break - } - - if err2 == io.ErrUnexpectedEOF { - if !cryptoRan { - text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(text[:n2], p[:n2]) - pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2) - copy(text[n2:], pad) - // ciphertext = padWithPKCS7(text) - // fmt.Println(n2, padErr) - - mode.CryptBlocks(ciphertext, text) - fmt.Println("ct1", ciphertext) - out.Write(ciphertext) - out.Close() - } else { - text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(text[:n2], p[:n2]) - pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2) - - copy(text[n2:], pad) - fmt.Println("text[n2:]", text) - // ciphertext = padWithPKCS7(text) - - mode.CryptBlocks(ciphertext, text) - fmt.Println("ct2", ciphertext, string(ciphertext)) - - out.Write(ciphertext) - - out.Close() - - } - - } - fmt.Println("Exiting For") - break - } - - ciphertext := make([]byte, blockSize) - cryptoRan = true - if contentRead >= contentLenIn { - // p, padErr := pkcs7Pad(p, blockSize) - pad := bytes.Repeat([]byte{byte(blockSize - n2)}, blockSize-n2) - copy(p[n2:], pad) - - fmt.Println("n2, padErr, ciphertext, p, blockSize", n2, pad, ciphertext, p, blockSize) - } - - mode.CryptBlocks(ciphertext, p) - - fmt.Println("ct0 write", ciphertext, string(ciphertext)) - out.Write(ciphertext) - - } - - out.Close() - filepathDecOutput := "file_upload_test_enc_dec.txt" - filepathDecOutput = "tux_out.jpg" - // filepathDecOutput = "gopher_out.jpg" - filepathDecOutput = "Moth_out.jpg" - // filepathDecOutput = "file_upload_test_out.txt" - out, _ = os.Open(filepathOutput) - - blockSize = 16 - bufferSize = 16 - p = make([]byte, bufferSize) - ivBuff := make([]byte, blockSize) - emptyByteVar := make([]byte, blockSize) - - iv2 := make([]byte, blockSize) - count := 0 - r, w := io.Pipe() - done := make(chan bool) - cryptoRan = false - - fi, _ := out.Stat() - contentLenEnc := fi.Size() - var contentDownloaded int64 - fmt.Println("contentLenEnc", contentLenEnc) - - go func() { - ExitReadLabel: - for { - n2, err2 := io.ReadFull(out, p) - fmt.Println("file contents ->", n2, len(p), p, contentDownloaded) - if err2 != nil { - fmt.Println("\nerr2:", err2) - if err2 == io.EOF { - ciphertext := make([]byte, blockSize) - copy(ciphertext, p[:n2]) - ciphertext, _ = unpadPKCS7(ciphertext) - w.Write(ciphertext) - // fmt.Println("ct2 EOF ", ciphertext, string(ciphertext), p[:n2], p, diffLen) - w.Close() - break ExitReadLabel - } - - if err2 == io.ErrUnexpectedEOF { - if bytes.Equal(iv2, emptyByteVar) { - copy(iv2, ivBuff[0:blockSize]) - fmt.Println("string IV err2:", string(iv2), n2) - fmt.Println("Extracted IV err2:", iv2, len(iv2)) - mode = cipher.NewCBCDecrypter(block, iv2) - done <- true - } - if !cryptoRan { - text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(text, p[:n2]) - mode.CryptBlocks(ciphertext, text) - ciphertext, _ = unpadPKCS7(ciphertext) - w.Write(ciphertext) - - w.Close() - break ExitReadLabel - } else { - // text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(ciphertext, p[:n2]) - ciphertext, _ = unpadPKCS7(ciphertext) - w.Write(ciphertext) - - // w.Write(ciphertextCopy) - - w.Close() - break ExitReadLabel - } - - } - fmt.Println("Exiting For") - break ExitReadLabel - } else { - contentDownloaded += int64(n2) - if count < blockSize/bufferSize { - fmt.Println(string(p[:n2])) - - // If error is not nil then panics - if err != nil { - panic(err) - } - copy(ivBuff[bufferSize*count:], p) - fmt.Println(string(ivBuff), n2) - } else { - - if bytes.Equal(iv2, emptyByteVar) { - copy(iv2, ivBuff[0:blockSize]) - fmt.Println("string IV:", string(iv2), n2) - fmt.Println("Extracted IV:", iv2, len(iv2)) - mode = cipher.NewCBCDecrypter(block, iv2) - done <- true - } - - ciphertext := make([]byte, blockSize) - - text := make([]byte, blockSize) - copy(text, p[:n2]) - diffLen := contentDownloaded - contentLenEnc - fmt.Println("p===>", p, text, n2, diffLen, contentDownloaded, contentLenEnc) - - mode.CryptBlocks(ciphertext, p) - cryptoRan = true - if contentDownloaded >= contentLenEnc { - ciphertext, _ = unpadPKCS7(ciphertext) - w.Write(ciphertext) - } else { - fmt.Println("ct0 read:", ciphertext, string(ciphertext), n2, p) - - w.Write(ciphertext) - } - } - } - count++ - - } - }() - fmt.Println("before done") - <-done - fmt.Println("after done") - - outD, _ := os.Create(filepathDecOutput) - io.Copy(outD, r) - - fileText, _ := ioutil.ReadFile(filepathInput) - fileTextOut, _ := ioutil.ReadFile(filepathDecOutput) - fileOut, err := os.Open(filepathInput) - fio, _ := fileOut.Stat() - contentLenOut := fio.Size() - fmt.Println(contentLenIn, contentLenEnc, contentLenOut, contentRead) - assert.Equal(contentLenIn, contentLenOut) - assert.Equal(contentLenIn, contentRead) - // fileTextEncoded, _ := ioutil.ReadFile("file_upload_test_enc_out.txt") - // fileTextEncoded2, _ := ioutil.ReadFile("whoami_enc.txt") - // assert.Equal(fileTextEncoded2, fileTextEncoded) - // fmt.Println("---FileText---") - // fmt.Println(string(fileText)) - // fmt.Println("---FileTextOut---") - // fmt.Println(string(fileTextOut)) - assert.Equal(fileText, fileTextOut) -} - -func FileDownload3Test(t *testing.T) { - //assert := assert.New(t) - b := []byte{136, 230, 36, 7, 53, 165, 35, 60, 127, 128, 184, 60, 131, 24, 6, 161, 41} - fmt.Println(string(b)) - - filepathInput := "video_enc.mp4" - filepathDecOutput := "video_enc_out.mp4" - filepathInput = "gif_test.gif" - filepathDecOutput = "gif_test_out.gif" - filepathInput = "file_test.png" - filepathDecOutput = "file_test_out.png" - filepathInput = "whoami.txt" - filepathDecOutput = "whoami_out.txt" - filepathInput = "file_upload_original_encrypted.txt" - filepathDecOutput = "file_upload_original_encrypted_out.txt" - - key := utils.EncryptCipherKey("enigma") - block, err := aes.NewCipher(key) - if err != nil { - panic(err) - } - - out, err := os.Open(filepathInput) - - blockSize := 16 - bufferSize := 16 - p := make([]byte, bufferSize) - ivBuff := make([]byte, blockSize) - emptyByteVar := make([]byte, blockSize) - - iv2 := make([]byte, blockSize) - count := 0 - r, w := io.Pipe() - - var mode cipher.BlockMode - - done := make(chan bool) - cryptoRan := false - fi, _ := out.Stat() - contentLenIn := fi.Size() - var contentDownloaded int64 - - go func() { - ExitReadLabel: - for { - n2, err2 := io.ReadFull(out, p) - fmt.Println("file contents ->", n2, len(p), p, contentDownloaded) - if err2 != nil { - fmt.Println("\nerr2:", err2) - if err2 == io.EOF { - ciphertext := make([]byte, blockSize) - copy(ciphertext, p) - fmt.Println("EOF EOF") - diffLen := int(contentDownloaded - contentLenIn) - fmt.Println("ct2 EOF ", ciphertext, string(ciphertext), p[:n2], p, diffLen) - w.Close() - break ExitReadLabel - } - - if err2 == io.ErrUnexpectedEOF { - if bytes.Equal(iv2, emptyByteVar) { - copy(iv2, ivBuff[0:blockSize]) - fmt.Println("string IV err2:", string(iv2), n2) - fmt.Println("Extracted IV err2:", iv2, len(iv2)) - mode = cipher.NewCBCDecrypter(block, iv2) - done <- true - } - if !cryptoRan { - text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(text, p[:n2]) - mode.CryptBlocks(ciphertext, text) - extra := contentLenIn % int64(bufferSize) - ciphertextCopy := make([]byte, extra) - copy(ciphertextCopy[:extra], ciphertext[:extra]) - fmt.Println("ct1 ", ciphertextCopy) - w.Write(ciphertextCopy) - w.Close() - break ExitReadLabel - } else { - text := make([]byte, blockSize) - ciphertext := make([]byte, blockSize) - copy(text, p[:n2]) - extra := contentLenIn % int64(bufferSize) - ciphertextCopy := make([]byte, extra) - copy(ciphertextCopy[:extra], ciphertext[:extra]) - fmt.Println("ct2 ", ciphertextCopy) - - fmt.Println("ct2 read", ciphertext, string(ciphertext)) - - w.Write(ciphertextCopy) - - w.Close() - break ExitReadLabel - } - - } - fmt.Println("Exiting For") - break ExitReadLabel - } else { - if count < blockSize/bufferSize { - fmt.Println(string(p[:n2])) - - // If error is not nil then panics - if err != nil { - panic(err) - } - copy(ivBuff[bufferSize*count:], p) - fmt.Println(string(ivBuff), n2) - } else { - contentDownloaded += int64(n2) - - if bytes.Equal(iv2, emptyByteVar) { - copy(iv2, ivBuff[0:blockSize]) - fmt.Println("string IV:", string(iv2), n2) - fmt.Println("Extracted IV:", iv2, len(iv2)) - mode = cipher.NewCBCDecrypter(block, iv2) - done <- true - } - - ciphertext := make([]byte, blockSize) - - text := make([]byte, blockSize) - copy(text, p[:n2]) - diffLen := contentDownloaded - contentLenIn - fmt.Println("p===>", p, text, n2, diffLen, contentDownloaded, contentLenIn) - - mode.CryptBlocks(ciphertext, p) - cryptoRan = true - if contentDownloaded > contentLenIn { - extra := contentLenIn % int64(bufferSize) - ciphertextCopy := make([]byte, extra) - copy(ciphertextCopy[:extra], ciphertext[:extra]) - fmt.Println("ciphertext ct01", ciphertextCopy, string(ciphertextCopy)) - fmt.Println("ct01 read:", ciphertext, string(ciphertext), n2, p) - w.Write(ciphertextCopy) - } else { - fmt.Println("ct0 read:", ciphertext, string(ciphertext), n2, p) - - w.Write(ciphertext) - } - } - } - count++ - - } - }() - fmt.Println("before done") - <-done - fmt.Println("after done") - - outD, _ := os.Create(filepathDecOutput) - io.Copy(outD, r) - - fmt.Println("after reader") - fmt.Println("---FileTextOut---") - // fmt.Println(string(fileTextOut)) - -} From c4a7128c867ed1230c0366c6a917fc19e6fa001f Mon Sep 17 00:00:00 2001 From: Lukasz Klich Date: Thu, 23 Feb 2023 11:23:30 +0100 Subject: [PATCH 3/3] Fix files test --- .gitignore | 1 + go.mod | 1 + go.sum | 2 ++ publish_file_message.go | 4 ++-- tests/e2e/files_test.go | 2 +- tests/e2e/helper.go | 3 +++ 6 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 38ac29fd..399b38b5 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.o *.a *.so +*.env # Folders _obj diff --git a/go.mod b/go.mod index 668431c8..4428c2ca 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/brianolson/cbor_go v1.0.0 github.com/cucumber/godog v0.12.0 github.com/google/uuid v1.3.0 + github.com/joho/godotenv v1.5.1 // indirect github.com/stretchr/testify v1.7.0 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d golang.org/x/text v0.3.7 // indirect diff --git a/go.sum b/go.sum index 95443213..d01c3777 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,8 @@ github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0m github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= diff --git a/publish_file_message.go b/publish_file_message.go index 24a116f7..083bea40 100644 --- a/publish_file_message.go +++ b/publish_file_message.go @@ -259,11 +259,11 @@ func (o *publishFileMessageOpts) buildQuery() (*url.Values, error) { q := defaultQuery(o.pubnub.Config.UUID, o.pubnub.telemetryManager) if o.MessageType != "" { - q.Set(publishMessageTypeQueryParam, utils.URLEncode(string(o.MessageType))) + q.Set(publishMessageTypeQueryParam, string(o.MessageType)) } if o.SpaceId != "" { - q.Set(publishSpaceIdQueryParam, utils.URLEncode(string(o.SpaceId))) + q.Set(publishSpaceIdQueryParam, string(o.SpaceId)) } SetQueryParam(q, o.QueryParam) diff --git a/tests/e2e/files_test.go b/tests/e2e/files_test.go index 3937d3ec..e9dcc9f4 100644 --- a/tests/e2e/files_test.go +++ b/tests/e2e/files_test.go @@ -70,7 +70,7 @@ func FileUploadCommon(t *testing.T, useCipher bool, customCipher string, filepat name := fmt.Sprintf("test_file_upload_name_%d.txt", rno) message := fmt.Sprintf("test file %s", name) expectedMessageType := pubnub.MessageType("This_is_messageType") - expectedSpaceId := pubnub.SpaceId("This_is spaceId") + expectedSpaceId := pubnub.SpaceId("This_is_spaceId") listener := pubnub.NewListener() exitListener := make(chan bool) diff --git a/tests/e2e/helper.go b/tests/e2e/helper.go index 1f24996f..4ff1ad0b 100644 --- a/tests/e2e/helper.go +++ b/tests/e2e/helper.go @@ -13,6 +13,7 @@ import ( "testing" "time" + godotenv "github.com/joho/godotenv" pubnub "github.com/pubnub/go/v7" "github.com/stretchr/testify/assert" ) @@ -43,6 +44,8 @@ func seedRand() { } func init() { + godotenv.Load("../../.env") + seedRand() config = pubnub.NewConfigWithUserId(pubnub.UserId(pubnub.GenerateUUID())) config.PublishKey = os.Getenv("PUBLISH_KEY")