From a798374d1238edff59134eaa294cbf803db96abf Mon Sep 17 00:00:00 2001 From: Simon Knott Date: Mon, 4 Nov 2024 17:13:40 +0100 Subject: [PATCH] proper string escaping --- packages/trace-viewer/src/ui/codegen.ts | 44 +++++++++---- tests/library/unit/codegen.spec.ts | 83 +++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 13 deletions(-) diff --git a/packages/trace-viewer/src/ui/codegen.ts b/packages/trace-viewer/src/ui/codegen.ts index 2d2c4936bc..3c8fc1e012 100644 --- a/packages/trace-viewer/src/ui/codegen.ts +++ b/packages/trace-viewer/src/ui/codegen.ts @@ -55,7 +55,7 @@ class JSCodeGen implements APIRequestCodegen { // Handle primitive types if (typeof obj !== 'object') { if (typeof obj === 'string') - return `'${obj}'`; + return this.stringLiteral(obj); return String(obj); } @@ -84,12 +84,17 @@ class JSCodeGen implements APIRequestCodegen { // Handle keys that need quotes const formattedKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : - `'${key}'`; + this.stringLiteral(key); return `${nextSpaces}${formattedKey}: ${formattedValue}`; }).join(',\n'); return `{\n${entries}\n${spaces}}`; } + + private stringLiteral(v: string): string { + v = v.replace(/\\/g, '\\\\').replace(/'/g, '\\\''); + return `'${v}'`; + } } class PythonCodeGen implements APIRequestCodegen { @@ -130,7 +135,7 @@ class PythonCodeGen implements APIRequestCodegen { // Handle primitive types if (typeof obj !== 'object') { if (typeof obj === 'string') - return `"${obj.replaceAll('"', '\\"')}"`; + return this.stringLiteral(obj); if (typeof obj === 'boolean') return obj ? 'True' : 'False'; return String(obj); @@ -158,11 +163,16 @@ class PythonCodeGen implements APIRequestCodegen { const entries = Object.entries(obj).map(([key, value]) => { const formattedValue = this.prettyPrintObject(value, indent, level + 1); - return `${nextSpaces}"${key}": ${formattedValue}`; + return `${nextSpaces}${this.stringLiteral(key)}: ${formattedValue}`; }).join(',\n'); return `{\n${entries}\n${spaces}}`; } + + private stringLiteral(v: string): string { + v = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return `"${v}"`; + } } class CSharpCodeGen implements APIRequestCodegen { @@ -198,10 +208,6 @@ class CSharpCodeGen implements APIRequestCodegen { return method[0].toUpperCase() + method.slice(1) + 'Async'; } - private indent(v: string, level: number): string { - return v.split('\n').map(s => ' '.repeat(level) + s).join('\n'); - } - private prettyPrintObject(obj: any, indent = 2, level = 0): string { // Handle null and undefined if (obj === null) @@ -212,7 +218,7 @@ class CSharpCodeGen implements APIRequestCodegen { // Handle primitive types if (typeof obj !== 'object') { if (typeof obj === 'string') - return `"${obj.replace(/"/g, '\\"')}"`; + return this.stringLiteral(obj); if (typeof obj === 'boolean') return obj ? 'true' : 'false'; return String(obj); @@ -240,12 +246,18 @@ class CSharpCodeGen implements APIRequestCodegen { const entries = Object.entries(obj).map(([key, value]) => { const formattedValue = this.prettyPrintObject(value, indent, level + 1); - const formattedKey = level === 0 ? key : `["${key}"]`; + const formattedKey = level === 0 ? key : `[${this.stringLiteral(key)}]`; return `${nextSpaces}${formattedKey} = ${formattedValue}`; }).join(',\n'); return `new() {\n${entries}\n${spaces}}`; } + + private stringLiteral(v: string): string { + // escape douvle quotes and backslashes + v = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return `"${v}"`; + } } class JavaCodeGen implements APIRequestCodegen { @@ -262,16 +274,22 @@ class JavaCodeGen implements APIRequestCodegen { } for (const [key, value] of url.searchParams) - options.push(`setQueryParam("${key}", "${value}")`); + options.push(`setQueryParam(${this.stringLiteral(key)}, ${this.stringLiteral(value)})`); if (body) - options.push(`setData("${body.replaceAll('"', '\\"')}")`); + options.push(`setData(${this.stringLiteral(body)})`); for (const header of request.headers) - options.push(`setHeader("${header.name}", "${header.value}")`); + options.push(`setHeader(${this.stringLiteral(header.name)}, ${this.stringLiteral(header.value)})`); if (options.length > 0) params.push(`RequestOptions.create()\n .${options.join('\n .')}\n`); return `request.${method}(${params.join(', ')});`; } + + private stringLiteral(v: string): string { + // escape douvle quotes and backslashes + v = v.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + return `"${v}"`; + } } export function getAPIRequestCodeGen(language: Language): APIRequestCodegen { diff --git a/tests/library/unit/codegen.spec.ts b/tests/library/unit/codegen.spec.ts index bb3240cc18..a4538747bd 100644 --- a/tests/library/unit/codegen.spec.ts +++ b/tests/library/unit/codegen.spec.ts @@ -201,6 +201,27 @@ await page.request.get('http://example.com/foo', { });`.trim()); }); + test('escape sequences', () => { + expect(impl.generatePlaywrightRequestCall({ + url: 'http://example.com/foo', + method: 'GET', + headers: [ + { name: 'F\\o', value: 'B\\r' }, + ], + httpVersion: '1.1', + cookies: [], + queryString: [], + headersSize: 0, + bodySize: 0, + comment: '', + }, undefined)).toEqual(` +await page.request.get('http://example.com/foo', { + headers: { + 'F\\\\o': 'B\\\\r' + } +});`.trim()); + }); + }); test.describe('python', () => { @@ -394,6 +415,28 @@ await page.request.get( )`.trim()); }); + test('escape sequences', () => { + expect(impl.generatePlaywrightRequestCall({ + url: 'http://example.com/foo', + method: 'GET', + headers: [ + { name: 'F\\o', value: 'B\\r' }, + ], + httpVersion: '1.1', + cookies: [], + queryString: [], + headersSize: 0, + bodySize: 0, + comment: '', + }, undefined)).toEqual(` +await page.request.get( + "http://example.com/foo", + headers={ + "F\\\\o": "B\\\\r" + } +)`.trim()); + }); + }); test.describe('csharp', () => { @@ -477,6 +520,27 @@ await request.PutAsync("http://example.com/foo", new() { Headers = new() { ["Content-Type"] = "application/json" } +});`.trim()); + }); + + test('escape sequences', () => { + expect(impl.generatePlaywrightRequestCall({ + url: 'http://example.com/foo', + method: 'GET', + headers: [ + { name: 'F\\o', value: 'B\\r' }, + ], + httpVersion: '1.1', + cookies: [], + queryString: [], + headersSize: 0, + bodySize: 0, + comment: '', + }, undefined)).toEqual(` +await request.GetAsync("http://example.com/foo", new() { + Headers = new() { + ["F\\\\o"] = "B\\\\r" + } });`.trim()); }); }); @@ -587,6 +651,25 @@ request.patch("http://example.com/foo", RequestOptions.create() }, undefined)).toEqual(` request.delete("http://example.com/foo", RequestOptions.create() .setHeader("Authorization", "Bearer token") +);`.trim()); + }); + + test('escape sequences', () => { + expect(impl.generatePlaywrightRequestCall({ + url: 'http://example.com/foo', + method: 'GET', + headers: [ + { name: 'F\\o', value: 'B\\r' }, + ], + httpVersion: '1.1', + cookies: [], + queryString: [], + headersSize: 0, + bodySize: 0, + comment: '', + }, undefined)).toEqual(` +request.get(\"http://example.com/foo\", RequestOptions.create() + .setHeader(\"F\\\\o\", \"B\\\\r\") );`.trim()); }); });