From b4d7bdad12d3f3ba4dc35461fb76f690a0a65c94 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 27 Jul 2022 11:44:58 +0200 Subject: [PATCH 1/5] Add string escaping helper method --- src/helpers/escape.test.ts | 35 ++++++++++++++++ src/helpers/escape.ts | 81 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 src/helpers/escape.test.ts create mode 100644 src/helpers/escape.ts diff --git a/src/helpers/escape.test.ts b/src/helpers/escape.test.ts new file mode 100644 index 000000000..4750ec381 --- /dev/null +++ b/src/helpers/escape.test.ts @@ -0,0 +1,35 @@ +import { escapeString } from './escape'; + +describe('Escape methods', () => { + describe('escapeString', () => { + it('does nothing to a safe string', () => { + expect( + escapeString("hello world") + ).toBe("hello world"); + }); + + it('escapes double quotes by default', () => { + expect( + escapeString('"hello world"') + ).toBe('\\"hello world\\"'); + }); + + it('escapes newlines by default', () => { + expect( + escapeString('hello\r\nworld') + ).toBe('hello\\r\\nworld'); + }); + + it('escapes backslashes', () => { + expect( + escapeString('hello\\world') + ).toBe('hello\\\\world'); + }); + + it('escapes unrepresentable characters', () => { + expect( + escapeString('hello \u0000') // 0 = ASCII 'null' character + ).toBe('hello \\u0000'); + }); + }); +}); diff --git a/src/helpers/escape.ts b/src/helpers/escape.ts new file mode 100644 index 000000000..4aba03630 --- /dev/null +++ b/src/helpers/escape.ts @@ -0,0 +1,81 @@ +export interface EscapeOptions { + /** + * The delimiter that will be used to wrap the string (and so must be escaped + * when used within the string). + * Defaults to " + */ + delimiter?: string; + + /** + * The char to use to escape the delimiter and other special characters. + * Defaults to \ + */ + escapeChar?: string; + + /** + * Whether newlines (\n and \r) should be escaped within the string. + * Defaults to true. + */ + escapeNewlines?: boolean; +} + +/** + * Escape characters within a value to make it safe to insert directly into a + * snippet. Takes options which define the escape requirements. + * + * This is closely based on the JSON-stringify string serialization algorithm, + * but generalized for other string delimiters (e.g. " or ') and different escape + * characters (e.g. Powershell uses `) + * + * See https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring + * for the complete original algorithm. + */ +export function escapeString(value: any, options: EscapeOptions = {}) { + const { + delimiter = '"', + escapeChar = '\\', + escapeNewlines = true + } = options; + + value = value.toString(); + + return [...value].map((c) => { + if (c === '\b') { + return escapeChar + 'b'; + } else if (c === '\t') { + return escapeChar + 't'; + } else if (c === '\n') { + if (escapeNewlines) { + return escapeChar + 'n'; + } else { + return c; // Don't just continue, or this is caught by < \u0020 + } + } else if (c === '\f') { + return escapeChar + 'f'; + } else if (c === '\r') { + if (escapeNewlines) { + return escapeChar + 'r'; + } else { + return c; // Don't just continue, or this is caught by < \u0020 + } + } else if (c === escapeChar) { + return escapeChar + escapeChar; + } else if (c === delimiter) { + return escapeChar + delimiter; + } else if (c < '\u0020' || c > '\u007E') { + // Delegate the trickier non-ASCII cases to the normal algorithm. Some of these + // are escaped as \uXXXX, whilst others are represented literally. Since we're + // using this primarily for header values that are generally (though not 100% + // strictly?) ASCII-only, this should almost never happen. + return JSON.stringify(c).slice(1, -1); + } else { + return c; + } + }).join(''); +} + +export const escapeForSingleQuotes = (value: any) => + escapeString(value, { delimiter: "'" }); + +export const escapeForDoubleQuotes = (value: any) => + escapeString(value, { delimiter: '"' }); From e659f4c87d94e8808ca447f93de47832070873a1 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 27 Jul 2022 12:07:34 +0200 Subject: [PATCH 2/5] Escape quotes appropriately in headers values in all snippets --- src/fixtures/requests/headers.json | 4 +- src/targets/c/libcurl/client.ts | 3 +- src/targets/c/libcurl/fixtures/headers.c | 2 +- .../clojure/clj_http/fixtures/headers.clj | 2 +- src/targets/csharp/httpclient/client.ts | 3 +- .../csharp/httpclient/fixtures/headers.cs | 2 +- src/targets/csharp/restsharp/client.ts | 3 +- .../csharp/restsharp/fixtures/headers.cs | 2 +- src/targets/go/native/client.ts | 3 +- src/targets/go/native/fixtures/headers.go | 2 +- src/targets/http/http1.1/fixtures/headers | 2 +- src/targets/java/asynchttp/client.ts | 3 +- .../java/asynchttp/fixtures/headers.java | 2 +- src/targets/java/nethttp/client.ts | 3 +- .../java/nethttp/fixtures/headers.java | 2 +- src/targets/java/okhttp/client.ts | 3 +- src/targets/java/okhttp/fixtures/headers.java | 2 +- src/targets/java/unirest/client.ts | 3 +- .../java/unirest/fixtures/headers.java | 2 +- .../javascript/axios/fixtures/headers.js | 6 ++- .../javascript/fetch/fixtures/headers.js | 6 ++- .../javascript/jquery/fixtures/headers.js | 2 +- src/targets/javascript/xhr/client.ts | 3 +- .../javascript/xhr/fixtures/headers.js | 2 +- src/targets/kotlin/okhttp/client.ts | 3 +- src/targets/kotlin/okhttp/fixtures/headers.kt | 2 +- src/targets/node/axios/fixtures/headers.js | 6 ++- src/targets/node/fetch/fixtures/headers.js | 6 ++- src/targets/node/native/fixtures/headers.js | 2 +- src/targets/node/request/fixtures/headers.js | 6 ++- src/targets/node/unirest/fixtures/headers.js | 2 +- .../objc/nsurlsession/fixtures/headers.m | 2 +- src/targets/ocaml/cohttp/client.ts | 5 +- src/targets/ocaml/cohttp/fixtures/headers.ml | 2 +- src/targets/php/curl/client.ts | 3 +- src/targets/php/curl/fixtures/headers.php | 2 +- src/targets/php/guzzle/client.ts | 5 +- src/targets/php/guzzle/fixtures/headers.php | 2 +- src/targets/php/helpers.ts | 4 +- src/targets/php/http1/fixtures/headers.php | 2 +- src/targets/php/http2/fixtures/headers.php | 2 +- src/targets/powershell/common.ts | 7 ++- .../restmethod/fixtures/headers.ps1 | 2 +- .../webrequest/fixtures/headers.ps1 | 2 +- src/targets/python/python3/client.ts | 7 +-- .../python/python3/fixtures/headers.py | 2 +- src/targets/python/requests/client.ts | 7 +-- .../python/requests/fixtures/headers.py | 2 +- src/targets/r/httr/client.ts | 54 ++++++++++--------- src/targets/r/httr/fixtures/headers.r | 2 +- src/targets/ruby/native/client.ts | 3 +- src/targets/ruby/native/fixtures/headers.rb | 2 +- src/targets/shell/curl/fixtures/headers.sh | 2 +- src/targets/shell/httpie/fixtures/headers.sh | 2 +- src/targets/shell/wget/fixtures/headers.sh | 2 +- .../swift/nsurlsession/fixtures/headers.swift | 2 +- 56 files changed, 133 insertions(+), 88 deletions(-) diff --git a/src/fixtures/requests/headers.json b/src/fixtures/requests/headers.json index a00f2907d..bdca60725 100644 --- a/src/fixtures/requests/headers.json +++ b/src/fixtures/requests/headers.json @@ -11,8 +11,8 @@ "value": "Bar" }, { - "name": "x-bar", - "value": "Foo" + "name": "quoted-value", + "value": "\"quoted\" 'string'" } ] } diff --git a/src/targets/c/libcurl/client.ts b/src/targets/c/libcurl/client.ts index 686aaed53..7f6e9b42d 100644 --- a/src/targets/c/libcurl/client.ts +++ b/src/targets/c/libcurl/client.ts @@ -1,4 +1,5 @@ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const libcurl: Client = { @@ -25,7 +26,7 @@ export const libcurl: Client = { push('struct curl_slist *headers = NULL;'); headers.forEach(header => { - push(`headers = curl_slist_append(headers, "${header}: ${headersObj[header]}");`); + push(`headers = curl_slist_append(headers, "${header}: ${escapeForDoubleQuotes(headersObj[header])}");`); }); push('curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);'); diff --git a/src/targets/c/libcurl/fixtures/headers.c b/src/targets/c/libcurl/fixtures/headers.c index 3d74ce4e8..7b48ac87a 100644 --- a/src/targets/c/libcurl/fixtures/headers.c +++ b/src/targets/c/libcurl/fixtures/headers.c @@ -6,7 +6,7 @@ curl_easy_setopt(hnd, CURLOPT_URL, "http://mockbin.com/har"); struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "accept: application/json"); headers = curl_slist_append(headers, "x-foo: Bar"); -headers = curl_slist_append(headers, "x-bar: Foo"); +headers = curl_slist_append(headers, "quoted-value: \"quoted\" 'string'"); curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers); CURLcode ret = curl_easy_perform(hnd); \ No newline at end of file diff --git a/src/targets/clojure/clj_http/fixtures/headers.clj b/src/targets/clojure/clj_http/fixtures/headers.clj index 8686c3156..3a6d8904c 100644 --- a/src/targets/clojure/clj_http/fixtures/headers.clj +++ b/src/targets/clojure/clj_http/fixtures/headers.clj @@ -1,5 +1,5 @@ (require '[clj-http.client :as client]) (client/get "http://mockbin.com/har" {:headers {:x-foo "Bar" - :x-bar "Foo"} + :quoted-value "\"quoted\" 'string'"} :accept :json}) \ No newline at end of file diff --git a/src/targets/csharp/httpclient/client.ts b/src/targets/csharp/httpclient/client.ts index 8026890e2..6f12b85ca 100644 --- a/src/targets/csharp/httpclient/client.ts +++ b/src/targets/csharp/httpclient/client.ts @@ -1,4 +1,5 @@ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { getHeader } from '../../../helpers/headers'; import { Request } from '../../../httpsnippet'; import { Client } from '../../targets'; @@ -102,7 +103,7 @@ export const httpclient: Client = { push('Headers =', 1); push('{', 1); headers.forEach(key => { - push(`{ "${key}", "${allHeaders[key]}" },`, 2); + push(`{ "${key}", "${escapeForDoubleQuotes(allHeaders[key])}" },`, 2); }); push('},', 1); } diff --git a/src/targets/csharp/httpclient/fixtures/headers.cs b/src/targets/csharp/httpclient/fixtures/headers.cs index 41456224e..21e4bcd1c 100644 --- a/src/targets/csharp/httpclient/fixtures/headers.cs +++ b/src/targets/csharp/httpclient/fixtures/headers.cs @@ -8,7 +8,7 @@ { { "accept", "application/json" }, { "x-foo", "Bar" }, - { "x-bar", "Foo" }, + { "quoted-value", "\"quoted\" 'string'" }, }, }; using (var response = await client.SendAsync(request)) diff --git a/src/targets/csharp/restsharp/client.ts b/src/targets/csharp/restsharp/client.ts index 1bc05cf66..15f2a6e83 100644 --- a/src/targets/csharp/restsharp/client.ts +++ b/src/targets/csharp/restsharp/client.ts @@ -1,4 +1,5 @@ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { getHeader } from '../../../helpers/headers'; import { Client } from '../../targets'; @@ -25,7 +26,7 @@ export const restsharp: Client = { // Add headers, including the cookies Object.keys(headersObj).forEach(key => { - push(`request.AddHeader("${key}", "${headersObj[key]}");`); + push(`request.AddHeader("${key}", "${escapeForDoubleQuotes(headersObj[key])}");`); }); cookies.forEach(({ name, value }) => { diff --git a/src/targets/csharp/restsharp/fixtures/headers.cs b/src/targets/csharp/restsharp/fixtures/headers.cs index a25f1ae4c..9a02c6db5 100644 --- a/src/targets/csharp/restsharp/fixtures/headers.cs +++ b/src/targets/csharp/restsharp/fixtures/headers.cs @@ -2,5 +2,5 @@ var request = new RestRequest(Method.GET); request.AddHeader("accept", "application/json"); request.AddHeader("x-foo", "Bar"); -request.AddHeader("x-bar", "Foo"); +request.AddHeader("quoted-value", "\"quoted\" 'string'"); IRestResponse response = client.Execute(request); \ No newline at end of file diff --git a/src/targets/go/native/client.ts b/src/targets/go/native/client.ts index 8339dc65e..62d4b697d 100644 --- a/src/targets/go/native/client.ts +++ b/src/targets/go/native/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export interface GoNativeOptions { @@ -125,7 +126,7 @@ export const native: Client = { // Add headers if (Object.keys(allHeaders).length) { Object.keys(allHeaders).forEach(key => { - push(`req.Header.Add("${key}", "${allHeaders[key]}")`, indent); + push(`req.Header.Add("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, indent); }); blank(); diff --git a/src/targets/go/native/fixtures/headers.go b/src/targets/go/native/fixtures/headers.go index 5b05a2314..96a6c4957 100644 --- a/src/targets/go/native/fixtures/headers.go +++ b/src/targets/go/native/fixtures/headers.go @@ -14,7 +14,7 @@ func main() { req.Header.Add("accept", "application/json") req.Header.Add("x-foo", "Bar") - req.Header.Add("x-bar", "Foo") + req.Header.Add("quoted-value", "\"quoted\" 'string'") res, _ := http.DefaultClient.Do(req) diff --git a/src/targets/http/http1.1/fixtures/headers b/src/targets/http/http1.1/fixtures/headers index c2d0a0f39..fd3da05f4 100644 --- a/src/targets/http/http1.1/fixtures/headers +++ b/src/targets/http/http1.1/fixtures/headers @@ -1,6 +1,6 @@ GET /har HTTP/1.1 Accept: application/json X-Foo: Bar -X-Bar: Foo +Quoted-Value: "quoted" 'string' Host: mockbin.com diff --git a/src/targets/java/asynchttp/client.ts b/src/targets/java/asynchttp/client.ts index fee21b2cc..6586290a0 100644 --- a/src/targets/java/asynchttp/client.ts +++ b/src/targets/java/asynchttp/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const asynchttp: Client = { @@ -31,7 +32,7 @@ export const asynchttp: Client = { // Add headers, including the cookies Object.keys(allHeaders).forEach(key => { - push(`.setHeader("${key}", "${allHeaders[key]}")`, 1); + push(`.setHeader("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, 1); }); if (postData.text) { diff --git a/src/targets/java/asynchttp/fixtures/headers.java b/src/targets/java/asynchttp/fixtures/headers.java index 52e505eef..46ea34f17 100644 --- a/src/targets/java/asynchttp/fixtures/headers.java +++ b/src/targets/java/asynchttp/fixtures/headers.java @@ -2,7 +2,7 @@ client.prepare("GET", "http://mockbin.com/har") .setHeader("accept", "application/json") .setHeader("x-foo", "Bar") - .setHeader("x-bar", "Foo") + .setHeader("quoted-value", "\"quoted\" 'string'") .execute() .toCompletableFuture() .thenAccept(System.out::println) diff --git a/src/targets/java/nethttp/client.ts b/src/targets/java/nethttp/client.ts index faff5ac2c..984c515dd 100644 --- a/src/targets/java/nethttp/client.ts +++ b/src/targets/java/nethttp/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export interface NetHttpOptions { @@ -34,7 +35,7 @@ export const nethttp: Client = { push(`.uri(URI.create("${fullUrl}"))`, 2); Object.keys(allHeaders).forEach(key => { - push(`.header("${key}", "${allHeaders[key]}")`, 2); + push(`.header("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, 2); }); if (postData.text) { diff --git a/src/targets/java/nethttp/fixtures/headers.java b/src/targets/java/nethttp/fixtures/headers.java index 148f9c157..b7488b51c 100644 --- a/src/targets/java/nethttp/fixtures/headers.java +++ b/src/targets/java/nethttp/fixtures/headers.java @@ -2,7 +2,7 @@ .uri(URI.create("http://mockbin.com/har")) .header("accept", "application/json") .header("x-foo", "Bar") - .header("x-bar", "Foo") + .header("quoted-value", "\"quoted\" 'string'") .method("GET", HttpRequest.BodyPublishers.noBody()) .build(); HttpResponse response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); diff --git a/src/targets/java/okhttp/client.ts b/src/targets/java/okhttp/client.ts index c39480cba..5b29de801 100644 --- a/src/targets/java/okhttp/client.ts +++ b/src/targets/java/okhttp/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const okhttp: Client = { @@ -62,7 +63,7 @@ export const okhttp: Client = { // Add headers, including the cookies Object.keys(allHeaders).forEach(key => { - push(`.addHeader("${key}", "${allHeaders[key]}")`, 1); + push(`.addHeader("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, 1); }); push('.build();', 1); diff --git a/src/targets/java/okhttp/fixtures/headers.java b/src/targets/java/okhttp/fixtures/headers.java index 2489f1964..a932760e2 100644 --- a/src/targets/java/okhttp/fixtures/headers.java +++ b/src/targets/java/okhttp/fixtures/headers.java @@ -5,7 +5,7 @@ .get() .addHeader("accept", "application/json") .addHeader("x-foo", "Bar") - .addHeader("x-bar", "Foo") + .addHeader("quoted-value", "\"quoted\" 'string'") .build(); Response response = client.newCall(request).execute(); \ No newline at end of file diff --git a/src/targets/java/unirest/client.ts b/src/targets/java/unirest/client.ts index 7e4f69c69..c35fa1b08 100644 --- a/src/targets/java/unirest/client.ts +++ b/src/targets/java/unirest/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const unirest: Client = { @@ -38,7 +39,7 @@ export const unirest: Client = { // Add headers, including the cookies Object.keys(allHeaders).forEach(key => { - push(`.header("${key}", "${allHeaders[key]}")`, 1); + push(`.header("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, 1); }); if (postData.text) { diff --git a/src/targets/java/unirest/fixtures/headers.java b/src/targets/java/unirest/fixtures/headers.java index 880528e33..1ed87640c 100644 --- a/src/targets/java/unirest/fixtures/headers.java +++ b/src/targets/java/unirest/fixtures/headers.java @@ -1,5 +1,5 @@ HttpResponse response = Unirest.get("http://mockbin.com/har") .header("accept", "application/json") .header("x-foo", "Bar") - .header("x-bar", "Foo") + .header("quoted-value", "\"quoted\" 'string'") .asString(); \ No newline at end of file diff --git a/src/targets/javascript/axios/fixtures/headers.js b/src/targets/javascript/axios/fixtures/headers.js index f61df7eff..10133f586 100644 --- a/src/targets/javascript/axios/fixtures/headers.js +++ b/src/targets/javascript/axios/fixtures/headers.js @@ -3,7 +3,11 @@ import axios from 'axios'; const options = { method: 'GET', url: 'http://mockbin.com/har', - headers: {accept: 'application/json', 'x-foo': 'Bar', 'x-bar': 'Foo'} + headers: { + accept: 'application/json', + 'x-foo': 'Bar', + 'quoted-value': '"quoted" \'string\'' + } }; try { diff --git a/src/targets/javascript/fetch/fixtures/headers.js b/src/targets/javascript/fetch/fixtures/headers.js index fde4eb112..5ef7abd13 100644 --- a/src/targets/javascript/fetch/fixtures/headers.js +++ b/src/targets/javascript/fetch/fixtures/headers.js @@ -1,7 +1,11 @@ const url = 'http://mockbin.com/har'; const options = { method: 'GET', - headers: {accept: 'application/json', 'x-foo': 'Bar', 'x-bar': 'Foo'} + headers: { + accept: 'application/json', + 'x-foo': 'Bar', + 'quoted-value': '"quoted" \'string\'' + } }; try { diff --git a/src/targets/javascript/jquery/fixtures/headers.js b/src/targets/javascript/jquery/fixtures/headers.js index 0230ebc05..8c7a2b81b 100644 --- a/src/targets/javascript/jquery/fixtures/headers.js +++ b/src/targets/javascript/jquery/fixtures/headers.js @@ -6,7 +6,7 @@ const settings = { headers: { accept: 'application/json', 'x-foo': 'Bar', - 'x-bar': 'Foo' + 'quoted-value': '"quoted" \'string\'' } }; diff --git a/src/targets/javascript/xhr/client.ts b/src/targets/javascript/xhr/client.ts index 7ea5039fc..43d91023f 100644 --- a/src/targets/javascript/xhr/client.ts +++ b/src/targets/javascript/xhr/client.ts @@ -11,6 +11,7 @@ import stringifyObject from 'stringify-object'; import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForSingleQuotes } from '../../../helpers/escape'; import { getHeader, getHeaderName, hasHeader } from '../../../helpers/headers'; import { Client } from '../../targets'; @@ -89,7 +90,7 @@ export const xhr: Client = { push(`xhr.open('${method}', '${fullUrl}');`); Object.keys(allHeaders).forEach(key => { - push(`xhr.setRequestHeader('${key}', '${allHeaders[key]}');`); + push(`xhr.setRequestHeader('${key}', '${escapeForSingleQuotes(allHeaders[key])}');`); }); blank(); diff --git a/src/targets/javascript/xhr/fixtures/headers.js b/src/targets/javascript/xhr/fixtures/headers.js index e4f00e4aa..cb5bd4212 100644 --- a/src/targets/javascript/xhr/fixtures/headers.js +++ b/src/targets/javascript/xhr/fixtures/headers.js @@ -12,6 +12,6 @@ xhr.addEventListener('readystatechange', function () { xhr.open('GET', 'http://mockbin.com/har'); xhr.setRequestHeader('accept', 'application/json'); xhr.setRequestHeader('x-foo', 'Bar'); -xhr.setRequestHeader('x-bar', 'Foo'); +xhr.setRequestHeader('quoted-value', '"quoted" \'string\''); xhr.send(data); \ No newline at end of file diff --git a/src/targets/kotlin/okhttp/client.ts b/src/targets/kotlin/okhttp/client.ts index d33c94eb0..59e2b5ec7 100644 --- a/src/targets/kotlin/okhttp/client.ts +++ b/src/targets/kotlin/okhttp/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const okhttp: Client = { @@ -63,7 +64,7 @@ export const okhttp: Client = { // Add headers, including the cookies Object.keys(allHeaders).forEach(key => { - push(`.addHeader("${key}", "${allHeaders[key]}")`, 1); + push(`.addHeader("${key}", "${escapeForDoubleQuotes(allHeaders[key])}")`, 1); }); push('.build()', 1); diff --git a/src/targets/kotlin/okhttp/fixtures/headers.kt b/src/targets/kotlin/okhttp/fixtures/headers.kt index 67986cd60..9653f079c 100644 --- a/src/targets/kotlin/okhttp/fixtures/headers.kt +++ b/src/targets/kotlin/okhttp/fixtures/headers.kt @@ -5,7 +5,7 @@ val request = Request.Builder() .get() .addHeader("accept", "application/json") .addHeader("x-foo", "Bar") - .addHeader("x-bar", "Foo") + .addHeader("quoted-value", "\"quoted\" 'string'") .build() val response = client.newCall(request).execute() \ No newline at end of file diff --git a/src/targets/node/axios/fixtures/headers.js b/src/targets/node/axios/fixtures/headers.js index b0f1860ea..21c0fde66 100644 --- a/src/targets/node/axios/fixtures/headers.js +++ b/src/targets/node/axios/fixtures/headers.js @@ -3,7 +3,11 @@ const axios = require('axios').default; const options = { method: 'GET', url: 'http://mockbin.com/har', - headers: {accept: 'application/json', 'x-foo': 'Bar', 'x-bar': 'Foo'} + headers: { + accept: 'application/json', + 'x-foo': 'Bar', + 'quoted-value': '"quoted" \'string\'' + } }; try { diff --git a/src/targets/node/fetch/fixtures/headers.js b/src/targets/node/fetch/fixtures/headers.js index ab38eb596..6d66f3944 100644 --- a/src/targets/node/fetch/fixtures/headers.js +++ b/src/targets/node/fetch/fixtures/headers.js @@ -3,7 +3,11 @@ const fetch = require('node-fetch'); const url = 'http://mockbin.com/har'; const options = { method: 'GET', - headers: {accept: 'application/json', 'x-foo': 'Bar', 'x-bar': 'Foo'} + headers: { + accept: 'application/json', + 'x-foo': 'Bar', + 'quoted-value': '"quoted" \'string\'' + } }; try { diff --git a/src/targets/node/native/fixtures/headers.js b/src/targets/node/native/fixtures/headers.js index 12ef4e695..9ae32dd79 100644 --- a/src/targets/node/native/fixtures/headers.js +++ b/src/targets/node/native/fixtures/headers.js @@ -8,7 +8,7 @@ const options = { headers: { accept: 'application/json', 'x-foo': 'Bar', - 'x-bar': 'Foo' + 'quoted-value': '"quoted" \'string\'' } }; diff --git a/src/targets/node/request/fixtures/headers.js b/src/targets/node/request/fixtures/headers.js index aba252086..df6daa6f8 100644 --- a/src/targets/node/request/fixtures/headers.js +++ b/src/targets/node/request/fixtures/headers.js @@ -3,7 +3,11 @@ const request = require('request'); const options = { method: 'GET', url: 'http://mockbin.com/har', - headers: {accept: 'application/json', 'x-foo': 'Bar', 'x-bar': 'Foo'} + headers: { + accept: 'application/json', + 'x-foo': 'Bar', + 'quoted-value': '"quoted" \'string\'' + } }; request(options, function (error, response, body) { diff --git a/src/targets/node/unirest/fixtures/headers.js b/src/targets/node/unirest/fixtures/headers.js index f2f267747..89752d34d 100644 --- a/src/targets/node/unirest/fixtures/headers.js +++ b/src/targets/node/unirest/fixtures/headers.js @@ -5,7 +5,7 @@ const req = unirest('GET', 'http://mockbin.com/har'); req.headers({ accept: 'application/json', 'x-foo': 'Bar', - 'x-bar': 'Foo' + 'quoted-value': '"quoted" \'string\'' }); req.end(function (res) { diff --git a/src/targets/objc/nsurlsession/fixtures/headers.m b/src/targets/objc/nsurlsession/fixtures/headers.m index a8c9bb5d1..f92af60c2 100644 --- a/src/targets/objc/nsurlsession/fixtures/headers.m +++ b/src/targets/objc/nsurlsession/fixtures/headers.m @@ -2,7 +2,7 @@ NSDictionary *headers = @{ @"accept": @"application/json", @"x-foo": @"Bar", - @"x-bar": @"Foo" }; + @"quoted-value": @"\"quoted\" 'string'" }; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://mockbin.com/har"] cachePolicy:NSURLRequestUseProtocolCachePolicy diff --git a/src/targets/ocaml/cohttp/client.ts b/src/targets/ocaml/cohttp/client.ts index 766961d8c..0528ead78 100644 --- a/src/targets/ocaml/cohttp/client.ts +++ b/src/targets/ocaml/cohttp/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export const cohttp: Client = { @@ -38,12 +39,12 @@ export const cohttp: Client = { if (headers.length === 1) { push( - `let headers = Header.add (Header.init ()) "${headers[0]}" "${allHeaders[headers[0]]}" in`, + `let headers = Header.add (Header.init ()) "${headers[0]}" "${escapeForDoubleQuotes(allHeaders[headers[0]])}" in`, ); } else if (headers.length > 1) { push('let headers = Header.add_list (Header.init ()) ['); headers.forEach(key => { - push(`("${key}", "${allHeaders[key]}");`, 1); + push(`("${key}", "${escapeForDoubleQuotes(allHeaders[key])}");`, 1); }); push('] in'); } diff --git a/src/targets/ocaml/cohttp/fixtures/headers.ml b/src/targets/ocaml/cohttp/fixtures/headers.ml index 3ac0292b9..e4a599e91 100644 --- a/src/targets/ocaml/cohttp/fixtures/headers.ml +++ b/src/targets/ocaml/cohttp/fixtures/headers.ml @@ -6,7 +6,7 @@ let uri = Uri.of_string "http://mockbin.com/har" in let headers = Header.add_list (Header.init ()) [ ("accept", "application/json"); ("x-foo", "Bar"); - ("x-bar", "Foo"); + ("quoted-value", "\"quoted\" 'string'"); ] in Client.call ~headers `GET uri diff --git a/src/targets/php/curl/client.ts b/src/targets/php/curl/client.ts index f9d4f74ab..170ffa33b 100644 --- a/src/targets/php/curl/client.ts +++ b/src/targets/php/curl/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; import { convertType } from '../helpers'; @@ -125,7 +126,7 @@ export const curl: Client = { // construct cookies const headers = Object.keys(headersObj) .sort() - .map(key => `"${key}: ${headersObj[key]}"`); + .map(key => `"${key}: ${escapeForDoubleQuotes(headersObj[key])}"`); if (headers.length) { curlopts.push('CURLOPT_HTTPHEADER => ['); diff --git a/src/targets/php/curl/fixtures/headers.php b/src/targets/php/curl/fixtures/headers.php index 0afa8ce9b..fc6bf8928 100644 --- a/src/targets/php/curl/fixtures/headers.php +++ b/src/targets/php/curl/fixtures/headers.php @@ -12,7 +12,7 @@ CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => [ "accept: application/json", - "x-bar: Foo", + "quoted-value: \"quoted\" 'string'", "x-foo: Bar" ], ]); diff --git a/src/targets/php/guzzle/client.ts b/src/targets/php/guzzle/client.ts index f95433015..6daf3effd 100644 --- a/src/targets/php/guzzle/client.ts +++ b/src/targets/php/guzzle/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForSingleQuotes } from '../../../helpers/escape'; import { getHeader, getHeaderName, hasHeader } from '../../../helpers/headers'; import { Client } from '../../targets'; import { convertType } from '../helpers'; @@ -122,7 +123,7 @@ export const guzzle: Client = { const headers = Object.keys(headersObj) .sort() .map(function (key) { - return `${opts.indent}${opts.indent}'${key}' => '${headersObj[key]}',`; + return `${opts.indent}${opts.indent}'${key}' => '${escapeForSingleQuotes(headersObj[key])}',`; }); // construct cookies @@ -130,7 +131,7 @@ export const guzzle: Client = { .map(cookie => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`) .join('; '); if (cookieString.length) { - headers.push(`${opts.indent}${opts.indent}'cookie' => '${cookieString}',`); + headers.push(`${opts.indent}${opts.indent}'cookie' => '${escapeForSingleQuotes(cookieString)}',`); } if (headers.length) { diff --git a/src/targets/php/guzzle/fixtures/headers.php b/src/targets/php/guzzle/fixtures/headers.php index 31439d7bd..799870490 100644 --- a/src/targets/php/guzzle/fixtures/headers.php +++ b/src/targets/php/guzzle/fixtures/headers.php @@ -5,7 +5,7 @@ $response = $client->request('GET', 'http://mockbin.com/har', [ 'headers' => [ 'accept' => 'application/json', - 'x-bar' => 'Foo', + 'quoted-value' => '"quoted" \'string\'', 'x-foo' => 'Bar', ], ]); diff --git a/src/targets/php/helpers.ts b/src/targets/php/helpers.ts index b86099f72..5d6670e9d 100644 --- a/src/targets/php/helpers.ts +++ b/src/targets/php/helpers.ts @@ -1,3 +1,5 @@ +import { escapeString } from "../../helpers/escape"; + export const convertType = (obj: any[] | any, indent?: string, lastIndent?: string) => { lastIndent = lastIndent || ''; indent = indent || ''; @@ -10,7 +12,7 @@ export const convertType = (obj: any[] | any, indent?: string, lastIndent?: stri return 'null'; case '[object String]': - return `'${obj.replace(/\\/g, '\\\\').replace(/'/g, "'")}'`; + return `'${escapeString(obj, { delimiter: "'", escapeNewlines: false })}'`; case '[object Number]': return obj.toString(); diff --git a/src/targets/php/http1/fixtures/headers.php b/src/targets/php/http1/fixtures/headers.php index 78b53ea50..bc84c6113 100644 --- a/src/targets/php/http1/fixtures/headers.php +++ b/src/targets/php/http1/fixtures/headers.php @@ -7,7 +7,7 @@ $request->setHeaders([ 'accept' => 'application/json', 'x-foo' => 'Bar', - 'x-bar' => 'Foo' + 'quoted-value' => '"quoted" \'string\'' ]); try { diff --git a/src/targets/php/http2/fixtures/headers.php b/src/targets/php/http2/fixtures/headers.php index 40979ed95..fd557629f 100644 --- a/src/targets/php/http2/fixtures/headers.php +++ b/src/targets/php/http2/fixtures/headers.php @@ -8,7 +8,7 @@ $request->setHeaders([ 'accept' => 'application/json', 'x-foo' => 'Bar', - 'x-bar' => 'Foo' + 'quoted-value' => '"quoted" \'string\'' ]); $client->enqueue($request)->send(); diff --git a/src/targets/powershell/common.ts b/src/targets/powershell/common.ts index cb1252d97..bba0789b9 100644 --- a/src/targets/powershell/common.ts +++ b/src/targets/powershell/common.ts @@ -1,4 +1,5 @@ import { CodeBuilder } from '../../helpers/code-builder'; +import { escapeString } from '../../helpers/escape'; import { getHeader } from '../../helpers/headers'; import { Converter } from '../targets'; @@ -32,7 +33,7 @@ export const generatePowershellConvert = (command: PowershellCommand) => { headers.forEach(key => { if (key !== 'connection') { // Not allowed - push(`$headers.Add("${key}", "${headersObj[key]}")`); + push(`$headers.Add("${key}", "${escapeString(headersObj[key], { escapeChar: '`' })}")`); } }); commandOptions.push('-Headers $headers'); @@ -55,7 +56,9 @@ export const generatePowershellConvert = (command: PowershellCommand) => { } if (postData.text) { - commandOptions.push(`-ContentType '${getHeader(allHeaders, 'content-type')}'`); + commandOptions.push(`-ContentType '${ + escapeString(getHeader(allHeaders, 'content-type'), { delimiter: "'", escapeChar: '`' }) + }'`); commandOptions.push(`-Body '${postData.text}'`); } diff --git a/src/targets/powershell/restmethod/fixtures/headers.ps1 b/src/targets/powershell/restmethod/fixtures/headers.ps1 index 938c9d938..319fc99f6 100644 --- a/src/targets/powershell/restmethod/fixtures/headers.ps1 +++ b/src/targets/powershell/restmethod/fixtures/headers.ps1 @@ -1,5 +1,5 @@ $headers=@{} $headers.Add("accept", "application/json") $headers.Add("x-foo", "Bar") -$headers.Add("x-bar", "Foo") +$headers.Add("quoted-value", "`"quoted`" 'string'") $response = Invoke-RestMethod -Uri 'http://mockbin.com/har' -Method GET -Headers $headers \ No newline at end of file diff --git a/src/targets/powershell/webrequest/fixtures/headers.ps1 b/src/targets/powershell/webrequest/fixtures/headers.ps1 index e7f1c0ca3..00f35d240 100644 --- a/src/targets/powershell/webrequest/fixtures/headers.ps1 +++ b/src/targets/powershell/webrequest/fixtures/headers.ps1 @@ -1,5 +1,5 @@ $headers=@{} $headers.Add("accept", "application/json") $headers.Add("x-foo", "Bar") -$headers.Add("x-bar", "Foo") +$headers.Add("quoted-value", "`"quoted`" 'string'") $response = Invoke-WebRequest -Uri 'http://mockbin.com/har' -Method GET -Headers $headers \ No newline at end of file diff --git a/src/targets/python/python3/client.ts b/src/targets/python/python3/client.ts index 41b0b9b23..e172403b1 100644 --- a/src/targets/python/python3/client.ts +++ b/src/targets/python/python3/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export interface Python3Options { @@ -55,7 +56,7 @@ export const python3: Client = { const headerCount = Object.keys(headers).length; if (headerCount === 1) { for (const header in headers) { - push(`headers = { '${header}': "${headers[header]}" }`); + push(`headers = { '${header}': "${escapeForDoubleQuotes(headers[header])}" }`); blank(); } } else if (headerCount > 1) { @@ -65,9 +66,9 @@ export const python3: Client = { for (const header in headers) { if (count++ !== headerCount) { - push(` '${header}': "${headers[header]}",`); + push(` '${header}': "${escapeForDoubleQuotes(headers[header])}",`); } else { - push(` '${header}': "${headers[header]}"`); + push(` '${header}': "${escapeForDoubleQuotes(headers[header])}"`); } } diff --git a/src/targets/python/python3/fixtures/headers.py b/src/targets/python/python3/fixtures/headers.py index 36ab3fc05..022c19636 100644 --- a/src/targets/python/python3/fixtures/headers.py +++ b/src/targets/python/python3/fixtures/headers.py @@ -5,7 +5,7 @@ headers = { 'accept': "application/json", 'x-foo': "Bar", - 'x-bar': "Foo" + 'quoted-value': "\"quoted\" 'string'" } conn.request("GET", "/har", headers=headers) diff --git a/src/targets/python/requests/client.ts b/src/targets/python/requests/client.ts index 829fcb8d7..24e8d42bb 100644 --- a/src/targets/python/requests/client.ts +++ b/src/targets/python/requests/client.ts @@ -9,6 +9,7 @@ */ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes } from '../../../helpers/escape'; import { getHeaderName } from '../../../helpers/headers'; import { Client } from '../../targets'; import { literalRepresentation } from '../helpers'; @@ -130,7 +131,7 @@ export const requests: Client = { blank(); } else if (headerCount === 1) { for (const header in headers) { - push(`headers = {"${header}": "${headers[header]}"}`); + push(`headers = {"${header}": "${escapeForDoubleQuotes(headers[header])}"}`); blank(); } } else if (headerCount > 1) { @@ -140,9 +141,9 @@ export const requests: Client = { for (const header in headers) { if (count !== headerCount) { - push(`"${header}": "${headers[header]}",`, 1); + push(`"${header}": "${escapeForDoubleQuotes(headers[header])}",`, 1); } else { - push(`"${header}": "${headers[header]}"`, 1); + push(`"${header}": "${escapeForDoubleQuotes(headers[header])}"`, 1); } count += 1; } diff --git a/src/targets/python/requests/fixtures/headers.py b/src/targets/python/requests/fixtures/headers.py index 40bca3959..4c4970731 100644 --- a/src/targets/python/requests/fixtures/headers.py +++ b/src/targets/python/requests/fixtures/headers.py @@ -5,7 +5,7 @@ headers = { "accept": "application/json", "x-foo": "Bar", - "x-bar": "Foo" + "quoted-value": "\"quoted\" 'string'" } response = requests.get(url, headers=headers) diff --git a/src/targets/r/httr/client.ts b/src/targets/r/httr/client.ts index 82b6d5199..0d1e92eab 100644 --- a/src/targets/r/httr/client.ts +++ b/src/targets/r/httr/client.ts @@ -14,6 +14,8 @@ export interface HttrOptions { } import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForDoubleQuotes, escapeForSingleQuotes } from '../../../helpers/escape'; +import { getHeader } from '../../../helpers/headers'; import { Client } from '../../targets'; export const httr: Client = { @@ -95,22 +97,32 @@ export const httr: Client = { } // Construct headers - const headers = []; - let cookies; - let accept; - - for (const head in allHeaders) { - if (head.toLowerCase() === 'accept') { - accept = `, accept("${allHeaders[head]}")`; - } else if (head.toLowerCase() === 'cookie') { - cookies = `, set_cookies(\`${String(allHeaders[head]) + const cookieHeader = getHeader(allHeaders, 'cookie'); + let acceptHeader = getHeader(allHeaders, 'accept'); + + const setCookies = cookieHeader + ? `set_cookies(\`${String(cookieHeader) .replace(/;/g, '", `') .replace(/` /g, '`') - .replace(/[=]/g, '` = "')}")`; - } else if (head.toLowerCase() !== 'content-type') { - headers.push(`'${head}' = '${allHeaders[head]}'`); - } - } + .replace(/[=]/g, '` = "') + }")` + : undefined + + const setAccept = acceptHeader + ? `accept("${escapeForDoubleQuotes(acceptHeader)}")` + : undefined + + const setContentType = `content_type("${escapeForDoubleQuotes(postData.mimeType)}")` + + const otherHeaders = Object.entries(allHeaders) + // These headers are all handled separately: + .filter(([key]) => !['cookie', 'accept', 'content-type'].includes(key.toLowerCase())) + .map(([key, value]) => `'${key}' = '${escapeForSingleQuotes(value)}'`) + .join(', ') + + const setHeaders = otherHeaders + ? `add_headers(${otherHeaders})` + : undefined // Construct request let request = `response <- VERB("${method}", url`; @@ -119,22 +131,14 @@ export const httr: Client = { request += ', body = payload'; } - if (headers.length) { - request += `, add_headers(${headers.join(', ')})`; - } - if (queryString.length) { request += ', query = queryString'; } - request += `, content_type("${postData.mimeType}")`; - - if (typeof accept !== 'undefined') { - request += accept; - } + const headerAdditions = [setHeaders, setContentType, setAccept, setCookies].filter(x => !!x).join(', '); - if (typeof cookies !== 'undefined') { - request += cookies; + if (headerAdditions) { + request += ', ' + headerAdditions } if (postData.text || postData.jsonObj || postData.params) { diff --git a/src/targets/r/httr/fixtures/headers.r b/src/targets/r/httr/fixtures/headers.r index 9b5b4ec14..865e1a020 100644 --- a/src/targets/r/httr/fixtures/headers.r +++ b/src/targets/r/httr/fixtures/headers.r @@ -2,6 +2,6 @@ library(httr) url <- "http://mockbin.com/har" -response <- VERB("GET", url, add_headers('x-foo' = 'Bar', 'x-bar' = 'Foo'), content_type("application/octet-stream"), accept("application/json")) +response <- VERB("GET", url, add_headers('x-foo' = 'Bar', 'quoted-value' = '"quoted" \'string\''), content_type("application/octet-stream"), accept("application/json")) content(response, "text") \ No newline at end of file diff --git a/src/targets/ruby/native/client.ts b/src/targets/ruby/native/client.ts index bc596a268..17a051129 100644 --- a/src/targets/ruby/native/client.ts +++ b/src/targets/ruby/native/client.ts @@ -1,4 +1,5 @@ import { CodeBuilder } from '../../../helpers/code-builder'; +import { escapeForSingleQuotes } from '../../../helpers/escape'; import { Client } from '../../targets'; export interface RubyNativeOptions { @@ -70,7 +71,7 @@ export const native: Client = { const headers = Object.keys(allHeaders); if (headers.length) { headers.forEach(key => { - push(`request["${key}"] = '${allHeaders[key]}'`); + push(`request["${key}"] = '${escapeForSingleQuotes(allHeaders[key])}'`); }); } diff --git a/src/targets/ruby/native/fixtures/headers.rb b/src/targets/ruby/native/fixtures/headers.rb index 9df47d4e0..9a9aa24f3 100644 --- a/src/targets/ruby/native/fixtures/headers.rb +++ b/src/targets/ruby/native/fixtures/headers.rb @@ -8,7 +8,7 @@ request = Net::HTTP::Get.new(url) request["accept"] = 'application/json' request["x-foo"] = 'Bar' -request["x-bar"] = 'Foo' +request["quoted-value"] = '"quoted" \'string\'' response = http.request(request) puts response.read_body \ No newline at end of file diff --git a/src/targets/shell/curl/fixtures/headers.sh b/src/targets/shell/curl/fixtures/headers.sh index 04db84c9b..94ab3603c 100644 --- a/src/targets/shell/curl/fixtures/headers.sh +++ b/src/targets/shell/curl/fixtures/headers.sh @@ -1,5 +1,5 @@ curl --request GET \ --url http://mockbin.com/har \ --header 'accept: application/json' \ - --header 'x-bar: Foo' \ + --header 'quoted-value: "quoted" '\''string'\''' \ --header 'x-foo: Bar' \ No newline at end of file diff --git a/src/targets/shell/httpie/fixtures/headers.sh b/src/targets/shell/httpie/fixtures/headers.sh index 961f3f8b5..0b40c3050 100644 --- a/src/targets/shell/httpie/fixtures/headers.sh +++ b/src/targets/shell/httpie/fixtures/headers.sh @@ -1,4 +1,4 @@ http GET http://mockbin.com/har \ accept:application/json \ - x-bar:Foo \ + quoted-value:'"quoted" '\''string'\''' \ x-foo:Bar \ No newline at end of file diff --git a/src/targets/shell/wget/fixtures/headers.sh b/src/targets/shell/wget/fixtures/headers.sh index d279ce120..b6b7911e4 100644 --- a/src/targets/shell/wget/fixtures/headers.sh +++ b/src/targets/shell/wget/fixtures/headers.sh @@ -2,6 +2,6 @@ wget --quiet \ --method GET \ --header 'accept: application/json' \ --header 'x-foo: Bar' \ - --header 'x-bar: Foo' \ + --header 'quoted-value: "quoted" '\''string'\''' \ --output-document \ - http://mockbin.com/har \ No newline at end of file diff --git a/src/targets/swift/nsurlsession/fixtures/headers.swift b/src/targets/swift/nsurlsession/fixtures/headers.swift index 88d356e57..39dcdef6e 100644 --- a/src/targets/swift/nsurlsession/fixtures/headers.swift +++ b/src/targets/swift/nsurlsession/fixtures/headers.swift @@ -3,7 +3,7 @@ import Foundation let headers = [ "accept": "application/json", "x-foo": "Bar", - "x-bar": "Foo" + "quoted-value": "\"quoted\" 'string'" ] let request = NSMutableURLRequest(url: NSURL(string: "http://mockbin.com/har")! as URL, From 03bee2f137db4c3821e6cad89d29c9019c3d1895 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Wed, 27 Jul 2022 11:44:39 +0200 Subject: [PATCH 3/5] Fix tiny Python snippet indentation issue --- src/targets/python/python3/client.ts | 2 +- src/targets/python/python3/fixtures/full.py | 2 +- src/targets/python/python3/fixtures/headers.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/targets/python/python3/client.ts b/src/targets/python/python3/client.ts index e172403b1..a61e4695d 100644 --- a/src/targets/python/python3/client.ts +++ b/src/targets/python/python3/client.ts @@ -72,7 +72,7 @@ export const python3: Client = { } } - push(' }'); + push('}'); blank(); } diff --git a/src/targets/python/python3/fixtures/full.py b/src/targets/python/python3/fixtures/full.py index 4efb485a0..e3d098915 100644 --- a/src/targets/python/python3/fixtures/full.py +++ b/src/targets/python/python3/fixtures/full.py @@ -8,7 +8,7 @@ 'cookie': "foo=bar; bar=baz", 'accept': "application/json", 'content-type': "application/x-www-form-urlencoded" - } +} conn.request("POST", "/har?foo=bar&foo=baz&baz=abc&key=value", payload, headers) diff --git a/src/targets/python/python3/fixtures/headers.py b/src/targets/python/python3/fixtures/headers.py index 022c19636..ce156ea17 100644 --- a/src/targets/python/python3/fixtures/headers.py +++ b/src/targets/python/python3/fixtures/headers.py @@ -6,7 +6,7 @@ 'accept': "application/json", 'x-foo': "Bar", 'quoted-value': "\"quoted\" 'string'" - } +} conn.request("GET", "/har", headers=headers) From ac2a9b8015814f40a3f90f70c1d036dd42e428c7 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 28 Jul 2022 12:32:30 +0200 Subject: [PATCH 4/5] Add documentation to escapeFor{Single,Double}Quotes helpers --- src/helpers/escape.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/helpers/escape.ts b/src/helpers/escape.ts index 4aba03630..69f5cfdb6 100644 --- a/src/helpers/escape.ts +++ b/src/helpers/escape.ts @@ -74,8 +74,22 @@ export function escapeString(value: any, options: EscapeOptions = {}) { }).join(''); } +/** + * Make a string value safe to insert literally into a snippet within single quotes, + * by escaping problematic characters, including single quotes inside the string, + * backslashes, newlines, and other special characters. + * + * If value is not a string, it will be stringified with .toString() first. + */ export const escapeForSingleQuotes = (value: any) => escapeString(value, { delimiter: "'" }); +/** + * Make a string value safe to insert literally into a snippet within double quotes, + * by escaping problematic characters, including double quotes inside the string, + * backslashes, newlines, and other special characters. + * + * If value is not a string, it will be stringified with .toString() first. + */ export const escapeForDoubleQuotes = (value: any) => escapeString(value, { delimiter: '"' }); From 1f518cafc684bb38ac636d09c65afd9601e59154 Mon Sep 17 00:00:00 2001 From: Tim Perry Date: Thu, 28 Jul 2022 12:33:18 +0200 Subject: [PATCH 5/5] Avoid reassigning 'value' in escapeString helper --- src/helpers/escape.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/escape.ts b/src/helpers/escape.ts index 69f5cfdb6..cbedfda90 100644 --- a/src/helpers/escape.ts +++ b/src/helpers/escape.ts @@ -30,16 +30,16 @@ export interface EscapeOptions { * See https://tc39.es/ecma262/multipage/structured-data.html#sec-quotejsonstring * for the complete original algorithm. */ -export function escapeString(value: any, options: EscapeOptions = {}) { +export function escapeString(rawValue: any, options: EscapeOptions = {}) { const { delimiter = '"', escapeChar = '\\', escapeNewlines = true } = options; - value = value.toString(); + const stringValue = rawValue.toString(); - return [...value].map((c) => { + return [...stringValue].map((c) => { if (c === '\b') { return escapeChar + 'b'; } else if (c === '\t') {