diff --git a/src/utils/__tests__/encoding-test.js b/src/utils/__tests__/encoding-test.js index 2c870c4e566..9d53c8cbeaf 100644 --- a/src/utils/__tests__/encoding-test.js +++ b/src/utils/__tests__/encoding-test.js @@ -5,6 +5,7 @@ import { hexToAscii, asciiToHex, xorHexStrings, + strToBase64, extractApiKey, } from '../encoding'; @@ -61,11 +62,40 @@ describe('asciiToHex', () => { }); }); +describe('strToBase64', () => { + test('can handle an empty string', () => { + const obj = ''; + const expected = ''; + + const result = strToBase64(obj); + + expect(result).toBe(expected); + }); + + test('can encode any string', () => { + const obj = { + key: 'ABCabc123', + empty: null, + array: [1, 2, 3], + }; + const expected = 'W29iamVjdCBPYmplY3Rd'; + + const result = strToBase64(obj); + + expect(result).toBe(expected); + }); + + test('supports unicode characters', () => { + const obj = { key: '😇😈' }; + const expected = 'W29iamVjdCBPYmplY3Rd'; + const result = strToBase64(obj); + expect(result).toBe(expected); + }); +}); describe('extractApiKey', () => { test('correctly extracts an API key that has been XORed with a OTP', () => { const key = 'testing'; const otp = 'A8348A93A83493'; - const encoded = xorHexStrings(asciiToHex(key), otp); expect(extractApiKey(encoded, otp)).toBe(key); }); diff --git a/src/utils/encoding.js b/src/utils/encoding.js index 3e2d9cdf8e2..6908022685a 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -32,6 +32,11 @@ export const base64ToHex = (bytes: string) => asciiToHex(base64.decode(bytes)); export const hexToBase64 = (hex: string) => base64.encode(hexToAscii(hex)); +// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding +// `base64.encode` used instead of `btoa` to support Unicode input +export const strToBase64 = (text: string): string => + base64.encode(unescape(encodeURIComponent(text))); + // Extract an API key encoded as a hex string XOR'ed with a one time pad (OTP) // (this is used during the OAuth flow) export const extractApiKey = (encoded: string, otp: string) => diff --git a/src/webview/MessageListWeb.js b/src/webview/MessageListWeb.js index dc760b11e1c..ee1d00e2660 100644 --- a/src/webview/MessageListWeb.js +++ b/src/webview/MessageListWeb.js @@ -10,6 +10,7 @@ import getHtml from './html/html'; import renderMessagesAsHtml from './html/renderMessagesAsHtml'; import { getInputMessages } from './webViewHandleUpdates'; import * as webViewEventHandlers from './webViewEventHandlers'; +import { strToBase64 } from '../utils/encoding'; export default class MessageListWeb extends Component { context: Context; @@ -29,7 +30,7 @@ export default class MessageListWeb extends Component { sendMessages = (messages: WebviewInputMessage[]): void => { if (this.webview && messages.length > 0) { - this.webview.postMessage(JSON.stringify(messages), '*'); + this.webview.postMessage(strToBase64(JSON.stringify(messages)), '*'); } }; diff --git a/src/webview/js/generatedEs3.js b/src/webview/js/generatedEs3.js index ae6ab929d3f..450df8a1b65 100644 --- a/src/webview/js/generatedEs3.js +++ b/src/webview/js/generatedEs3.js @@ -241,7 +241,8 @@ var messageHandlers = { document.addEventListener('message', function (e) { scrollEventsDisabled = true; - var messages = JSON.parse(e.data); + var decodedData = decodeURIComponent(escape(window.atob(e.data))); + var messages = JSON.parse(decodedData); messages.forEach(function (msg) { messageHandlers[msg.type](msg); }); diff --git a/src/webview/js/js.js b/src/webview/js/js.js index 6e8e01bb21d..dd1983bb6d9 100644 --- a/src/webview/js/js.js +++ b/src/webview/js/js.js @@ -261,7 +261,8 @@ const messageHandlers = { document.addEventListener('message', e => { scrollEventsDisabled = true; // $FlowFixMe - const messages: WebviewInputMessage[] = JSON.parse(e.data); + const decodedData = decodeURIComponent(escape(window.atob(e.data))); + const messages: WebviewInputMessage[] = JSON.parse(decodedData); messages.forEach((msg: WebviewInputMessage) => { // $FlowFixMe messageHandlers[msg.type](msg);