Skip to content

Commit e901d61

Browse files
Benjschofilfreire
andauthored
Add rust target with reqwest (#328)
* Create initial rust files Create initial rust files to support the target. Create all of the demo fixture files for the different request types. These also require specific `Cargo.toml` dependencies with some features, but I've tried to keep them fully qualified where possible. I'm not sure yet how best to show adding dependencies to a project to enable these features. * Start work on reqwest client Start working on the reqwest conversion client after adding targets. * Complete Adding Rust as target Completed adding Rust as a target. All of the fixtures have been tested in a separate Rust project to verify that they build and successfully run against the Har test endpoint. All tests are running and passing, except for the snapshot that verifies all available targets, not sure where to update that. * Run linter Ran the linter defined in `package.json` All tests except snapshot for targets passing. * Update snapshot to fix available targets test Update the available targets test to fix the snapshot. * lint --------- Co-authored-by: Filipe Freire <[email protected]>
1 parent 143bb6d commit e901d61

40 files changed

+897
-89
lines changed

Diff for: .github/workflows/build.yml

+3-4
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515
jobs:
1616
scan:
1717
permissions:
18-
packages: write
18+
packages: write
1919
contents: write # publish sbom to GH releases/tag assets
2020
runs-on: ubuntu-latest
2121
steps:
@@ -74,14 +74,14 @@ jobs:
7474
- name: Checkout code
7575
uses: actions/checkout@v4
7676
with:
77-
fetch-depth: 0
77+
fetch-depth: 0
7878

7979
- name: Setup Node.js
8080
uses: actions/setup-node@v4
8181
with:
8282
node-version: 20.9.0
8383
registry-url: 'https://registry.npmjs.org'
84-
84+
8585
- name: Install
8686
run: npm ci
8787

@@ -92,4 +92,3 @@ jobs:
9292
run: npm publish --no-git-checks --provenance --tag ${{ github.sha }}
9393
env:
9494
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
95-

Diff for: .github/workflows/release.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
id: core_tag_and_release
5353
with:
5454
tag: v${{ env.TAG }}
55-
name: "httpsnippet v${{ env.TAG }} 📦"
55+
name: 'httpsnippet v${{ env.TAG }} 📦'
5656
generateReleaseNotes: true
5757
prerelease: false
58-
draft: false
58+
draft: false

Diff for: .github/workflows/sast.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ on:
44
pull_request: {}
55
push:
66
branches:
7-
- master
7+
- master
88
workflow_dispatch: {}
99

10-
1110
jobs:
1211
semgrep:
1312
name: Semgrep SAST

Diff for: SECURITY.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ At HTTPSnippet, we take security issues very seriously. If you believe you have
77
## How to Report
88

99
1. **Do not publicly disclose the vulnerability**: Please do not create a GitHub issue or post the vulnerability on public forums. Instead, contact us directly at [[email protected]](mailto:[email protected]).
10-
2. **Provide detailed information**: When reporting a vulnerability, please include as much information as possible to help us understand and reproduce the issue. This may include:
10+
1. **Provide detailed information**: When reporting a vulnerability, please include as much information as possible to help us understand and reproduce the issue. This may include:
1111
- Description of the vulnerability
1212
- Steps to reproduce the issue
1313
- Potential impact

Diff for: package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"request",
3131
"requests",
3232
"ruby",
33+
"rust",
3334
"shell",
3435
"snippet",
3536
"swift",
@@ -72,8 +73,8 @@
7273
"eslint-plugin-jest": "^26.1.3",
7374
"eslint-plugin-jest-formatting": "^3.1.0",
7475
"eslint-plugin-simple-import-sort": "^7.0.0",
75-
"markdownlint-cli2": "^0.5.1",
7676
"jest": "^27.5.1",
77+
"markdownlint-cli2": "^0.5.1",
7778
"prettier": "^2.6.2",
7879
"ts-jest": "^27.1.4",
7980
"type-fest": "^2.12.2",

Diff for: src/helpers/__snapshots__/utils.test.ts.snap

+14
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,20 @@ Array [
322322
"key": "ruby",
323323
"title": "Ruby",
324324
},
325+
Object {
326+
"clients": Array [
327+
Object {
328+
"description": "reqwest HTTP library",
329+
"key": "reqwest",
330+
"link": "https://docs.rs/reqwest/latest/reqwest/",
331+
"title": "reqwest",
332+
},
333+
],
334+
"default": "reqwest",
335+
"extname": ".rs",
336+
"key": "rust",
337+
"title": "Rust",
338+
},
325339
Object {
326340
"clients": Array [
327341
Object {

Diff for: src/helpers/code-builder.ts

+12
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,18 @@ export class CodeBuilder {
5656
this.code.push(newLine);
5757
};
5858

59+
/**
60+
* Add the line to the end of the last line. Creates a new line
61+
* if no lines exist yet.
62+
*/
63+
pushToLast = (line: string) => {
64+
if (!this.code) {
65+
this.push(line);
66+
}
67+
const updatedLine = `${this.code[this.code.length - 1]}${line}`;
68+
this.code[this.code.length - 1] = updatedLine;
69+
};
70+
5971
/**
6072
* Add an empty line at the end of current lines
6173
*/

Diff for: src/helpers/escape.test.ts

+6-14
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,26 @@
11
import { escapeString } from './escape';
22

3-
describe('Escape methods', () => {
3+
describe('escape methods', () => {
44
describe('escapeString', () => {
55
it('does nothing to a safe string', () => {
6-
expect(
7-
escapeString("hello world")
8-
).toBe("hello world");
6+
expect(escapeString('hello world')).toBe('hello world');
97
});
108

119
it('escapes double quotes by default', () => {
12-
expect(
13-
escapeString('"hello world"')
14-
).toBe('\\"hello world\\"');
10+
expect(escapeString('"hello world"')).toBe('\\"hello world\\"');
1511
});
1612

1713
it('escapes newlines by default', () => {
18-
expect(
19-
escapeString('hello\r\nworld')
20-
).toBe('hello\\r\\nworld');
14+
expect(escapeString('hello\r\nworld')).toBe('hello\\r\\nworld');
2115
});
2216

2317
it('escapes backslashes', () => {
24-
expect(
25-
escapeString('hello\\world')
26-
).toBe('hello\\\\world');
18+
expect(escapeString('hello\\world')).toBe('hello\\\\world');
2719
});
2820

2921
it('escapes unrepresentable characters', () => {
3022
expect(
31-
escapeString('hello \u0000') // 0 = ASCII 'null' character
23+
escapeString('hello \u0000'), // 0 = ASCII 'null' character
3224
).toBe('hello \\u0000');
3325
});
3426
});

Diff for: src/helpers/escape.ts

+31-38
Original file line numberDiff line numberDiff line change
@@ -31,47 +31,42 @@ export interface EscapeOptions {
3131
* for the complete original algorithm.
3232
*/
3333
export function escapeString(rawValue: any, options: EscapeOptions = {}) {
34-
const {
35-
delimiter = '"',
36-
escapeChar = '\\',
37-
escapeNewlines = true
38-
} = options;
34+
const { delimiter = '"', escapeChar = '\\', escapeNewlines = true } = options;
3935

4036
const stringValue = rawValue.toString();
4137

42-
return [...stringValue].map((c) => {
43-
if (c === '\b') {
44-
return escapeChar + 'b';
45-
} else if (c === '\t') {
46-
return escapeChar + 't';
47-
} else if (c === '\n') {
48-
if (escapeNewlines) {
49-
return escapeChar + 'n';
50-
} else {
38+
return [...stringValue]
39+
.map(c => {
40+
if (c === '\b') {
41+
return `${escapeChar}b`;
42+
} else if (c === '\t') {
43+
return `${escapeChar}t`;
44+
} else if (c === '\n') {
45+
if (escapeNewlines) {
46+
return `${escapeChar}n`;
47+
}
5148
return c; // Don't just continue, or this is caught by < \u0020
52-
}
53-
} else if (c === '\f') {
54-
return escapeChar + 'f';
55-
} else if (c === '\r') {
56-
if (escapeNewlines) {
57-
return escapeChar + 'r';
58-
} else {
49+
} else if (c === '\f') {
50+
return `${escapeChar}f`;
51+
} else if (c === '\r') {
52+
if (escapeNewlines) {
53+
return `${escapeChar}r`;
54+
}
5955
return c; // Don't just continue, or this is caught by < \u0020
56+
} else if (c === escapeChar) {
57+
return escapeChar + escapeChar;
58+
} else if (c === delimiter) {
59+
return escapeChar + delimiter;
60+
} else if (c < '\u0020' || c > '\u007E') {
61+
// Delegate the trickier non-ASCII cases to the normal algorithm. Some of these
62+
// are escaped as \uXXXX, whilst others are represented literally. Since we're
63+
// using this primarily for header values that are generally (though not 100%
64+
// strictly?) ASCII-only, this should almost never happen.
65+
return JSON.stringify(c).slice(1, -1);
6066
}
61-
} else if (c === escapeChar) {
62-
return escapeChar + escapeChar;
63-
} else if (c === delimiter) {
64-
return escapeChar + delimiter;
65-
} else if (c < '\u0020' || c > '\u007E') {
66-
// Delegate the trickier non-ASCII cases to the normal algorithm. Some of these
67-
// are escaped as \uXXXX, whilst others are represented literally. Since we're
68-
// using this primarily for header values that are generally (though not 100%
69-
// strictly?) ASCII-only, this should almost never happen.
70-
return JSON.stringify(c).slice(1, -1);
71-
} else {
7267
return c;
73-
}
74-
}).join('');
68+
})
69+
.join('');
7570
}
7671

7772
/**
@@ -81,8 +76,7 @@ export function escapeString(rawValue: any, options: EscapeOptions = {}) {
8176
*
8277
* If value is not a string, it will be stringified with .toString() first.
8378
*/
84-
export const escapeForSingleQuotes = (value: any) =>
85-
escapeString(value, { delimiter: "'" });
79+
export const escapeForSingleQuotes = (value: any) => escapeString(value, { delimiter: "'" });
8680

8781
/**
8882
* Make a string value safe to insert literally into a snippet within double quotes,
@@ -91,5 +85,4 @@ export const escapeForSingleQuotes = (value: any) =>
9185
*
9286
* If value is not a string, it will be stringified with .toString() first.
9387
*/
94-
export const escapeForDoubleQuotes = (value: any) =>
95-
escapeString(value, { delimiter: '"' });
88+
export const escapeForDoubleQuotes = (value: any) => escapeString(value, { delimiter: '"' });

Diff for: src/helpers/headers.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { ValueOf } from 'type-fest';
2-
31
type Headers<T> = Record<string, T>;
42

53
/**

Diff for: src/targets/c/libcurl/client.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,11 @@ export const libcurl: Client = {
2626
push('struct curl_slist *headers = NULL;');
2727

2828
headers.forEach(header => {
29-
push(`headers = curl_slist_append(headers, "${header}: ${escapeForDoubleQuotes(headersObj[header])}");`);
29+
push(
30+
`headers = curl_slist_append(headers, "${header}: ${escapeForDoubleQuotes(
31+
headersObj[header],
32+
)}");`,
33+
);
3034
});
3135

3236
push('curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);');

Diff for: src/targets/ocaml/cohttp/client.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ export const cohttp: Client = {
3939

4040
if (headers.length === 1) {
4141
push(
42-
`let headers = Header.add (Header.init ()) "${headers[0]}" "${escapeForDoubleQuotes(allHeaders[headers[0]])}" in`,
42+
`let headers = Header.add (Header.init ()) "${headers[0]}" "${escapeForDoubleQuotes(
43+
allHeaders[headers[0]],
44+
)}" in`,
4345
);
4446
} else if (headers.length > 1) {
4547
push('let headers = Header.add_list (Header.init ()) [');

Diff for: src/targets/php/guzzle/client.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,19 @@ export const guzzle: Client<GuzzleOptions> = {
123123
const headers = Object.keys(headersObj)
124124
.sort()
125125
.map(function (key) {
126-
return `${opts.indent}${opts.indent}'${key}' => '${escapeForSingleQuotes(headersObj[key])}',`;
126+
return `${
127+
opts.indent
128+
}${opts.indent}'${key}' => '${escapeForSingleQuotes(headersObj[key])}',`;
127129
});
128130

129131
// construct cookies
130132
const cookieString = cookies
131133
.map(cookie => `${encodeURIComponent(cookie.name)}=${encodeURIComponent(cookie.value)}`)
132134
.join('; ');
133135
if (cookieString.length) {
134-
headers.push(`${opts.indent}${opts.indent}'cookie' => '${escapeForSingleQuotes(cookieString)}',`);
136+
headers.push(
137+
`${opts.indent}${opts.indent}'cookie' => '${escapeForSingleQuotes(cookieString)}',`,
138+
);
135139
}
136140

137141
if (headers.length) {

Diff for: src/targets/php/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { escapeString } from "../../helpers/escape";
1+
import { escapeString } from '../../helpers/escape';
22

33
export const convertType = (obj: any[] | any, indent?: string, lastIndent?: string) => {
44
lastIndent = lastIndent || '';

Diff for: src/targets/powershell/common.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ export const generatePowershellConvert = (command: PowershellCommand) => {
5656
}
5757

5858
if (postData.text) {
59-
commandOptions.push(`-ContentType '${
60-
escapeString(getHeader(allHeaders, 'content-type'), { delimiter: "'", escapeChar: '`' })
61-
}'`);
59+
commandOptions.push(
60+
`-ContentType '${escapeString(getHeader(allHeaders, 'content-type'), {
61+
delimiter: "'",
62+
escapeChar: '`',
63+
})}'`,
64+
);
6265
commandOptions.push(`-Body '${postData.text}'`);
6366
}
6467

Diff for: src/targets/r/httr/client.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -98,31 +98,26 @@ export const httr: Client = {
9898

9999
// Construct headers
100100
const cookieHeader = getHeader(allHeaders, 'cookie');
101-
let acceptHeader = getHeader(allHeaders, 'accept');
101+
const acceptHeader = getHeader(allHeaders, 'accept');
102102

103103
const setCookies = cookieHeader
104104
? `set_cookies(\`${String(cookieHeader)
105105
.replace(/;/g, '", `')
106106
.replace(/` /g, '`')
107-
.replace(/[=]/g, '` = "')
108-
}")`
109-
: undefined
107+
.replace(/[=]/g, '` = "')}")`
108+
: undefined;
110109

111-
const setAccept = acceptHeader
112-
? `accept("${escapeForDoubleQuotes(acceptHeader)}")`
113-
: undefined
110+
const setAccept = acceptHeader ? `accept("${escapeForDoubleQuotes(acceptHeader)}")` : undefined;
114111

115-
const setContentType = `content_type("${escapeForDoubleQuotes(postData.mimeType)}")`
112+
const setContentType = `content_type("${escapeForDoubleQuotes(postData.mimeType)}")`;
116113

117114
const otherHeaders = Object.entries(allHeaders)
118115
// These headers are all handled separately:
119116
.filter(([key]) => !['cookie', 'accept', 'content-type'].includes(key.toLowerCase()))
120117
.map(([key, value]) => `'${key}' = '${escapeForSingleQuotes(value)}'`)
121-
.join(', ')
118+
.join(', ');
122119

123-
const setHeaders = otherHeaders
124-
? `add_headers(${otherHeaders})`
125-
: undefined
120+
const setHeaders = otherHeaders ? `add_headers(${otherHeaders})` : undefined;
126121

127122
// Construct request
128123
let request = `response <- VERB("${method}", url`;
@@ -135,10 +130,12 @@ export const httr: Client = {
135130
request += ', query = queryString';
136131
}
137132

138-
const headerAdditions = [setHeaders, setContentType, setAccept, setCookies].filter(x => !!x).join(', ');
133+
const headerAdditions = [setHeaders, setContentType, setAccept, setCookies]
134+
.filter(x => !!x)
135+
.join(', ');
139136

140137
if (headerAdditions) {
141-
request += ', ' + headerAdditions
138+
request += `, ${headerAdditions}`;
142139
}
143140

144141
if (postData.text || postData.jsonObj || postData.params) {

0 commit comments

Comments
 (0)