From dbbc12c09621df485b15ec01b5ac770c0a8a0579 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Mon, 15 Sep 2025 14:51:50 -0400 Subject: [PATCH 1/2] Use Textdecoder when decoding code param payload --- .../workspace/src/lib/actions/workspace.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/libs/remix-ui/workspace/src/lib/actions/workspace.ts b/libs/remix-ui/workspace/src/lib/actions/workspace.ts index d136a6a5348..6b3358ad798 100644 --- a/libs/remix-ui/workspace/src/lib/actions/workspace.ts +++ b/libs/remix-ui/workspace/src/lib/actions/workspace.ts @@ -271,20 +271,14 @@ export type UrlParametersType = { } /** - * Decode a base64‑encoded string that was produced by - * percent‑escaping UTF‑8 bytes and then encoded with btoa(). + * Decode a base64‑encoded string that was produced by TextEncoder with btoa(). * * @param {string} b64Payload The base64 payload you got from params.code - * @returns {string} The original UTF‑8 string */ -export const decodePercentEscapedBase64 = (b64Payload: string) => { - const rawByteString = atob(b64Payload); - - const percentEscapedString = rawByteString.split('') - .map(c => '%' + c.charCodeAt(0).toString(16).padStart(2, '0')) - .join('') - - return decodeURIComponent(percentEscapedString); +export const decodeBase64 = (b64Payload: string) => { + const raw = atob(decodeURIComponent(b64Payload)); + const bytes = Uint8Array.from(raw, c => c.charCodeAt(0)); + return new TextDecoder().decode(bytes); } export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDefault', opts?) => { @@ -304,7 +298,7 @@ export const loadWorkspacePreset = async (template: WorkspaceTemplate = 'remixDe const hashed = bytesToHex(hash.keccakFromString(params.code)) path = 'contract-' + hashed.replace('0x', '').substring(0, 10) + (params.language && params.language.toLowerCase() === 'yul' ? '.yul' : '.sol') - content = decodePercentEscapedBase64(params.code) + content = decodeBase64(params.code) await workspaceProvider.set(path, content) } if (params.shareCode) { From 9082411d222b5e72d531153fea49b9c6dd89e031 Mon Sep 17 00:00:00 2001 From: CoveMB Date: Thu, 18 Sep 2025 20:47:32 -0400 Subject: [PATCH 2/2] Add test case --- apps/remix-ide-e2e/src/tests/url.test.ts | 36 ++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/apps/remix-ide-e2e/src/tests/url.test.ts b/apps/remix-ide-e2e/src/tests/url.test.ts index b35647cdffd..8e00f424e29 100644 --- a/apps/remix-ide-e2e/src/tests/url.test.ts +++ b/apps/remix-ide-e2e/src/tests/url.test.ts @@ -201,6 +201,42 @@ module.exports = { }) }, + 'Should load the code from URL & code params with ASCII punctuation and special character #group1': function (browser: NightwatchBrowser) { + browser + .url('http://127.0.0.1:8080/?#code=Ly8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IE1JVAovLyBDb21wYXRpYmxlIHdpdGggT3BlblplcHBlbGluIENvbnRyYWN0cyBeNS40LjAKcHJhZ21hIHNvbGlkaXR5IF4wLjguMjc7CgppbXBvcnQge0Fic3RyYWN0U2lnbmVyfSBmcm9tICJAb3BlbnplcHBlbGluL2NvbnRyYWN0c0A1LjQuMC91dGlscy9jcnlwdG9ncmFwaHkvc2lnbmVycy9BYnN0cmFjdFNpZ25lci5zb2wiOwppbXBvcnQge0FjY291bnR9IGZyb20gIkBvcGVuemVwcGVsaW4vY29udHJhY3RzQDUuNC4wL2FjY291bnQvQWNjb3VudC5zb2wiOwppbXBvcnQge0FjY291bnRFUkM3NTc5fSBmcm9tICJAb3BlbnplcHBlbGluL2NvbnRyYWN0c0A1LjQuMC9hY2NvdW50L2V4dGVuc2lvbnMvZHJhZnQtQWNjb3VudEVSQzc1Nzkuc29sIjsKaW1wb3J0IHtFSVA3MTJ9IGZyb20gIkBvcGVuemVwcGVsaW4vY29udHJhY3RzQDUuNC4wL3V0aWxzL2NyeXB0b2dyYXBoeS9FSVA3MTIuc29sIjsKaW1wb3J0IHtFUkMxMTU1SG9sZGVyfSBmcm9tICJAb3BlbnplcHBlbGluL2NvbnRyYWN0c0A1LjQuMC90b2tlbi9FUkMxMTU1L3V0aWxzL0VSQzExNTVIb2xkZXIuc29sIjsKaW1wb3J0IHtFUkM3MjFIb2xkZXJ9IGZyb20gIkBvcGVuemVwcGVsaW4vY29udHJhY3RzQDUuNC4wL3Rva2VuL0VSQzcyMS91dGlscy9FUkM3MjFIb2xkZXIuc29sIjsKaW1wb3J0IHtFUkM3NzM5fSBmcm9tICJAb3BlbnplcHBlbGluL2NvbnRyYWN0c0A1LjQuMC91dGlscy9jcnlwdG9ncmFwaHkvc2lnbmVycy9kcmFmdC1FUkM3NzM5LnNvbCI7CmltcG9ydCB7UGFja2VkVXNlck9wZXJhdGlvbn0gZnJvbSAiQG9wZW56ZXBwZWxpbi9jb250cmFjdHNANS40LjAvaW50ZXJmYWNlcy9kcmFmdC1JRVJDNDMzNy5zb2wiOwppbXBvcnQge1NpZ25lckVDRFNBfSBmcm9tICJAb3BlbnplcHBlbGluL2NvbnRyYWN0c0A1LjQuMC91dGlscy9jcnlwdG9ncmFwaHkvc2lnbmVycy9TaWduZXJFQ0RTQS5zb2wiOwoKY29udHJhY3QgTXlBY2NvdW50IGlzIEFjY291bnQsIEVJUDcxMiwgRVJDNzczOSwgQWNjb3VudEVSQzc1NzksIFNpZ25lckVDRFNBLCBFUkM3MjFIb2xkZXIsIEVSQzExNTVIb2xkZXIgewogICAgY29uc3RydWN0b3IoYWRkcmVzcyBzaWduZXIpCiAgICAgICAgRUlQNzEyKHVuaWNvZGUiTXlBY2NvdW508J%2BMviIsICIxIikKICAgICAgICBTaWduZXJFQ0RTQShzaWduZXIpCiAgICB7fQoKICAgIGZ1bmN0aW9uIGlzVmFsaWRTaWduYXR1cmUoYnl0ZXMzMiBoYXNoLCBieXRlcyBjYWxsZGF0YSBzaWduYXR1cmUpCiAgICAgICAgcHVibGljCiAgICAgICAgdmlldwogICAgICAgIG92ZXJyaWRlKEFjY291bnRFUkM3NTc5LCBFUkM3NzM5KQogICAgICAgIHJldHVybnMgKGJ5dGVzNCkKICAgIHsKICAgICAgICAvLyBFUkMtNzczOSBjYW4gcmV0dXJuIHRoZSBFUkMtMTI3MSBtYWdpYyB2YWx1ZSwgMHhmZmZmZmZmZiAoaW52YWxpZCkgb3IgMHg3NzM5MDAwMSAoZGV0ZWN0aW9uKS4KICAgICAgICAvLyBJZiB0aGUgcmV0dXJuZWQgdmFsdWUgaXMgMHhmZmZmZmZmZiwgZmFsbGJhY2sgdG8gRVJDLTc1NzkgdmFsaWRhdGlvbi4KICAgICAgICBieXRlczQgZXJjNzczOW1hZ2ljID0gRVJDNzczOS5pc1ZhbGlkU2lnbmF0dXJlKGhhc2gsIHNpZ25hdHVyZSk7CiAgICAgICAgcmV0dXJuIGVyYzc3MzltYWdpYyA9PSBieXRlczQoMHhmZmZmZmZmZikgPyBBY2NvdW50RVJDNzU3OS5pc1ZhbGlkU2lnbmF0dXJlKGhhc2gsIHNpZ25hdHVyZSkgOiBlcmM3NzM5bWFnaWM7CiAgICB9CgogICAgLy8gVGhlIGZvbGxvd2luZyBmdW5jdGlvbnMgYXJlIG92ZXJyaWRlcyByZXF1aXJlZCBieSBTb2xpZGl0eS4KCiAgICBmdW5jdGlvbiBfdmFsaWRhdGVVc2VyT3AoUGFja2VkVXNlck9wZXJhdGlvbiBjYWxsZGF0YSB1c2VyT3AsIGJ5dGVzMzIgdXNlck9wSGFzaCkKICAgICAgICBpbnRlcm5hbAogICAgICAgIG92ZXJyaWRlKEFjY291bnQsIEFjY291bnRFUkM3NTc5KQogICAgICAgIHJldHVybnMgKHVpbnQyNTYpCiAgICB7CiAgICAgICAgcmV0dXJuIHN1cGVyLl92YWxpZGF0ZVVzZXJPcCh1c2VyT3AsIHVzZXJPcEhhc2gpOwogICAgfQoKICAgIC8vIElNUE9SVEFOVDogTWFrZSBzdXJlIFNpZ25lckVDRFNBIGlzIG1vc3QgZGVyaXZlZCB0aGFuIEFjY291bnRFUkM3NTc5CiAgICAvLyBpbiB0aGUgaW5oZXJpdGFuY2UgY2hhaW4gKGkuZS4gY29udHJhY3QgLi4uIGlzIEFjY291bnRFUkM3NTc5LCAuLi4sIFNpZ25lckVDRFNBKQogICAgLy8gdG8gZW5zdXJlIHRoZSBjb3JyZWN0IG9yZGVyIG9mIGZ1bmN0aW9uIHJlc29sdXRpb24uCiAgICAvLyBBY2NvdW50RVJDNzU3OSByZXR1cm5zIGZhbHNlIGZvciBfcmF3U2lnbmF0dXJlVmFsaWRhdGlvbgogICAgZnVuY3Rpb24gX3Jhd1NpZ25hdHVyZVZhbGlkYXRpb24oYnl0ZXMzMiBoYXNoLCBieXRlcyBjYWxsZGF0YSBzaWduYXR1cmUpCiAgICAgICAgaW50ZXJuYWwKICAgICAgICB2aWV3CiAgICAgICAgb3ZlcnJpZGUoU2lnbmVyRUNEU0EsIEFic3RyYWN0U2lnbmVyLCBBY2NvdW50RVJDNzU3OSkKICAgICAgICByZXR1cm5zIChib29sKQogICAgewogICAgICAgIHJldHVybiBzdXBlci5fcmF3U2lnbmF0dXJlVmFsaWRhdGlvbihoYXNoLCBzaWduYXR1cmUpOwogICAgfQp9Cg&lang=en&optimize=false&runs=200&evmVersion=null') + .refreshPage() // we do one reload for making sure we already have the default workspace + + .waitForElementVisible('[data-id="compilerContainerCompileBtn"]') + .clickLaunchIcon('filePanel') + .waitForElementVisible({ + selector: `//li[@data-id="treeViewLitreeViewItemcontract-e0bf950259.sol"]`, + locateStrategy: 'xpath' + }) + .currentWorkspaceIs('code-sample') + .waitForElementVisible({ + selector: '//*[@id="editorView"]', + locateStrategy: 'xpath' + }) + .getEditorValue((content) => { + browser.assert.ok(content && content.indexOf( + '"MyAccount🌾"') !== -1, + 'code has been loaded') + }) + .getEditorValue((content) => { + browser.assert.ok(content && content.indexOf( + 'return erc7739magic == bytes4(0xffffffff) ? AccountERC7579.isValidSignature(hash, signature) : erc7739magic;') !== -1, + 'Account code has been loaded') + }) + .url('http://127.0.0.1:8080') // refresh without loading the code sample + .pause(2000) + .currentWorkspaceIs('default_workspace') + .execute(() => { + return document.querySelector('[data-id="dropdown-item-code-sample"]') === null + }, [], (result) => { + browser.assert.ok((result as any).value, 'sample template has not be persisted.') // code-sample should not be kept. + }) + }, + 'Should load the code from language & code params #group1': function (browser: NightwatchBrowser) { browser