Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
66baca6
Total Overhaul (but with all the same fixtures!) (#248)
dimitropoulos Apr 26, 2022
c7eae4c
fix: case where if `postData.params` is missing some targets crash (#…
erunion May 13, 2022
998e403
fix: compatibility issues on node 14 with `Object.hasOwn()` (#252)
erunion May 13, 2022
f64080d
fix: typo in the httpie `style` option not being correctly applied (#…
erunion May 13, 2022
cd4bb4c
fix: axios targets not sending `x-www-form-urlencoded` properly (#255)
erunion May 19, 2022
62d158d
feat: addition of a PHP target for Guzzle (#253)
erunion May 19, 2022
0e0728a
Add Github Build Workflow (#250) (#251)
filfreire May 20, 2022
411844f
feat: native upload support in python `requests` snippets (#259)
erunion May 23, 2022
3119335
fix: `multipart/form-data` header issues with node/js fetch targets (…
erunion May 23, 2022
5e8f161
fix: headers not being properly applied to R httr snippets (#263)
erunion May 23, 2022
8ca9771
Fix build workflow dispatch rules (#265)
filfreire May 24, 2022
97731b1
Chore: Remove travis links (#266)
filfreire May 26, 2022
91a872b
fix: issue where query strings in R wouldn't be properly concatenated…
erunion Jul 13, 2022
d4bebe4
add header namesspace to prevent header errors (#247)
Jul 13, 2022
da711e9
fix: stop implicitly coercing warning in Swift snippet generator (#195)
irajtaghlidi Jul 14, 2022
3c9e6a6
fix: clj-http handling of literal null JSON bodies (#283)
dimitropoulos Jul 15, 2022
7da8c97
fix: prevent crash in Swift/Objc with checking length of input body p…
irajtaghlidi Jul 15, 2022
12aa17e
fix: cUrl target should encode x-www-form-urlencoded post data params…
jgiovaresco Jul 15, 2022
2c8b558
feat: Add support for insecureSkipVerify (#285)
dimitropoulos Jul 18, 2022
735e69a
feat: implementing cleaner handling of JSON in cURL snippets (#256)
erunion Jul 18, 2022
8ea7e13
chore: minor cleanup (#286)
erunion Jul 18, 2022
19f1b2f
feat: use curl's --compressed option for requests that accept it (#287)
dimitropoulos Jul 18, 2022
2f7525d
feat: make Python snippets simpler, clearer & more consistent (#288)
dimitropoulos Jul 18, 2022
4ac5253
feat: change the default response code for Python Requests (#181)
jgiovaresco Jul 19, 2022
a92b4fc
feat: PHP JSON body encoding (#291)
dimitropoulos Jul 19, 2022
5ec84e0
Async/Await (top level) support in JavaScript snippets (#292)
dimitropoulos Jul 19, 2022
1dda869
Exclude package.json from build to fix output paths (#294)
pimterry Jul 26, 2022
911ab77
Fix crash when building nsurlsession snippets for empty params (#295)
pimterry Jul 26, 2022
cd2a0cc
removes `require 'openssl'` from ruby target (no longer needed) (#296)
dimitropoulos Jul 27, 2022
bf019b3
Escape quotes in headers correctly in all languages (#289)
pimterry Jul 28, 2022
7c00d05
updates README (#299)
dimitropoulos Aug 30, 2022
cee79a5
ioutil -> io (deprecated) (#305)
Nov 2, 2022
f289c7d
Merge remote-tracking branch 'upstream/master' into rebase/upstream
erunion Jun 16, 2023
20da8c8
chore: undoing unwanted changes
erunion Jun 16, 2023
69141c7
chore: revert more unwanted changes
erunion Jun 16, 2023
1d3265e
chore: reverting more unwanted changes
erunion Jun 16, 2023
903367e
fix: fixing broken test snapshots and libcurl not escaping
erunion Jun 16, 2023
08fc41e
fix: a bunch of broken tests
erunion Jun 16, 2023
1d8b372
fix: removing dead code
erunion Jun 16, 2023
9def8af
fix: remove support for `insecureSkipVerify` as we dont need or want it
erunion Jun 16, 2023
ba3a0b7
fix: removing top-level await changes for axios
erunion Jun 16, 2023
c8c0afd
fix: revert top-level await changes for js:fetch
erunion Jun 16, 2023
9b8e863
fix: remove some problematic changes to `node:request`
erunion Jun 16, 2023
eb26f40
fix: retaining line trimming in powershell snippets
erunion Jun 16, 2023
6b49e04
fix: bug in php snippets where booleans were casted to null
erunion Jun 16, 2023
5d083ab
fix: revert problematic changes to python:requests
erunion Jun 16, 2023
0171351
fix: revert more unwanted changes
erunion Jun 16, 2023
7b85447
chore: temporarily skipping the integration suite
erunion Jun 16, 2023
f6c9d4f
fix: broken snapshot
erunion Jun 16, 2023
5a328a6
fix: disabling the integration suite from being run without inside do…
erunion Jun 16, 2023
5c4c8c4
fix: integration suite
erunion Jun 16, 2023
5e56dbc
fix: integration suite
erunion Jun 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,15 @@ There are some major differences between this library and the [httpsnippet](http
* Does not do any HAR schema validation. It's just assumed that the HAR you're supplying to the library is already valid.
* The main `HTTPSnippet` export contains an `options` argument for an `harIsAlreadyEncoded` option for disabling [escaping](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent) of cookies and query strings in URLs.
* We added this because all HARs that we interact with already have this data escaped and this option prevents them from being double encoded, thus corrupting the data.
* Does not support the `insecureSkipVerify` option on `go:native`, `node:native`, `ruby:native`, and `shell:curl` as we don't want snippets generated for our users to bypass SSL certificate verification.
* Node
* `fetch`
* Body payloads are treated as an object literal and wrapped within `JSON.stringify()`. We do this to keep those targets looking nicer with those kinds of payloads. This also applies to the JS `fetch` target as wel.
* Body payloads are treated as an object literal and wrapped within `JSON.stringify()`. We do this to keep those targets looking nicer with those kinds of payloads. This also applies to the JS `fetch` target as well.
* `request`
* Does not provide query string parameters in a `params` argument due to complexities with query encoding.
* PHP → `guzzle`
* Snippets have `require_once('vendor/autoload.php');` prefixed at the top.
* PHP
* `guzzle`
* Snippets have `require_once('vendor/autoload.php');` prefixed at the top.
* Python
* `python3`
* Does not ship this client due to its incompatibility with being able to support file uploads.
Expand Down
6 changes: 3 additions & 3 deletions src/fixtures/requests/application-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module.exports = {
],
postData: {
mimeType: 'application/json',
text: '{"number":1,"string":"f\\"oo","arr":[1,2,3],"nested":{"a":"b"},"arr_mix":[1,"a",{"arr_mix_nested":{}}],"boolean":false}',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to this part of this fixture to an empty array because the empty object was throwing off some of our unit test assertions.

text: '{"number":1,"string":"f\\"oo","arr":[1,2,3],"nested":{"a":"b"},"arr_mix":[1,"a",{"arr_mix_nested":[]}],"boolean":false}',
},
},
response: {
Expand All @@ -36,7 +36,7 @@ module.exports = {
mimeType: 'application/json',
text: JSON.stringify({
args: {},
data: '{\n "number": 1,\n "string": "f\\"oo",\n "arr": [\n 1,\n 2,\n 3\n ],\n "nested": {\n "a": "b"\n },\n "arr_mix": [\n 1,\n "a",\n {\n "arr_mix_nested": {}\n }\n ],\n "boolean": false\n}',
data: '{\n "number": 1,\n "string": "f\\"oo",\n "arr": [\n 1,\n 2,\n 3\n ],\n "nested": {\n "a": "b"\n },\n "arr_mix": [\n 1,\n "a",\n {\n "arr_mix_nested": []\n }\n ],\n "boolean": false\n}',
files: {},
form: {},
headers: {
Expand All @@ -48,7 +48,7 @@ module.exports = {
1,
'a',
{
arr_mix_nested: {},
arr_mix_nested: [],
},
],
boolean: false,
Expand Down
4 changes: 4 additions & 0 deletions src/fixtures/requests/headers.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ module.exports = {
name: 'x-bar',
value: 'Foo',
},
{
name: 'quoted-value',
value: '"quoted" \'string\'',
},
],
},
response: {
Expand Down
27 changes: 27 additions & 0 deletions src/helpers/escape.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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');
});
});
});
91 changes: 91 additions & 0 deletions src/helpers/escape.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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(rawValue: any, options: EscapeOptions = {}) {
const { delimiter = '"', escapeChar = '\\', escapeNewlines = true } = options;

const stringValue = rawValue.toString();

return [...stringValue]
.map(c => {
if (c === '\b') {
return `${escapeChar}b`;
} else if (c === '\t') {
return `${escapeChar}t`;
} else if (c === '\n') {
if (escapeNewlines) {
return `${escapeChar}n`;
}

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`;
}

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);
}

return c;
})
.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: '"' });
2 changes: 1 addition & 1 deletion src/integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ availableTargets()
.filter(target => target.cli)
.filter(testFilter('key', environmentFilter()))
.forEach(({ key: targetId, cli: targetCLI, title, extname: fixtureExtension, clients }) => {
describe(`${title} integration tests`, () => {
(process.env.NODE_ENV === 'test' ? describe.skip : describe)(`${title} integration tests`, () => {
clients.filter(testFilter('key', clientFilter(targetId))).forEach(({ key: clientId }) => {
// If we're in an HTTPBin-powered Docker environment we only want to run tests for the
// client that our Docker has been configured for.
Expand Down
3 changes: 2 additions & 1 deletion src/targets/c/libcurl/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Client } from '../../targets';

import { CodeBuilder } from '../../../helpers/code-builder';
import { escapeForDoubleQuotes } from '../../../helpers/escape';

export const libcurl: Client = {
info: {
Expand All @@ -26,7 +27,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);');
Expand Down
2 changes: 1 addition & 1 deletion src/targets/c/libcurl/fixtures/application-json.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "content-type: application/json");
curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);

curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}],\"boolean\":false}");
curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}");

CURLcode ret = curl_easy_perform(hnd);
1 change: 1 addition & 0 deletions src/targets/c/libcurl/fixtures/headers.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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);
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
:string "f\"oo"
:arr [1 2 3]
:nested {:a "b"}
:arr_mix [1 "a" {:arr_mix_nested {}}]
:arr_mix [1 "a" {:arr_mix_nested []}]
:boolean false}})
3 changes: 2 additions & 1 deletion src/targets/clojure/clj_http/fixtures/headers.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
(require '[clj-http.client :as client])

(client/get "https://httpbin.org/headers" {:headers {:x-foo "Bar"
:x-bar "Foo"}
:x-bar "Foo"
:quoted-value "\"quoted\" 'string'"}
:accept :json})
3 changes: 2 additions & 1 deletion src/targets/csharp/httpclient/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Request } from '../../..';
import type { Client } from '../../targets';

import { CodeBuilder } from '../../../helpers/code-builder';
import { escapeForDoubleQuotes } from '../../../helpers/escape';
import { getHeader } from '../../../helpers/headers';

const getDecompressionMethods = (allHeaders: Request['allHeaders']) => {
Expand Down Expand Up @@ -103,7 +104,7 @@ export const httpclient: Client = {
push('Headers =', 1);
push('{', 1);
headers.forEach(key => {
push(`{ "${String(key)}", "${allHeaders[key]}" },`, 2);
push(`{ "${key}", "${escapeForDoubleQuotes(allHeaders[key])}" },`, 2);
});
push('},', 1);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
{
Method = HttpMethod.Post,
RequestUri = new Uri("https://httpbin.org/anything"),
Content = new StringContent("{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}],\"boolean\":false}")
Content = new StringContent("{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}")
{
Headers =
{
Expand Down
1 change: 1 addition & 0 deletions src/targets/csharp/httpclient/fixtures/headers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
{ "accept", "application/json" },
{ "x-foo", "Bar" },
{ "x-bar", "Foo" },
{ "quoted-value", "\"quoted\" 'string'" },
},
};
using (var response = await client.SendAsync(request))
Expand Down
3 changes: 2 additions & 1 deletion src/targets/csharp/restsharp/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Client } from '../../targets';

import { CodeBuilder } from '../../../helpers/code-builder';
import { escapeForDoubleQuotes } from '../../../helpers/escape';
import { getHeader } from '../../../helpers/headers';

export const restsharp: Client = {
Expand All @@ -26,7 +27,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 }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var client = new RestClient("https://httpbin.org/anything");
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
request.AddParameter("application/json", "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}],\"boolean\":false}", ParameterType.RequestBody);
request.AddParameter("application/json", "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":[]}],\"boolean\":false}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
1 change: 1 addition & 0 deletions src/targets/csharp/restsharp/fixtures/headers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
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);
Loading