Skip to content

Commit

Permalink
Merge remote-tracking branch 'sindresorhus/master' into beforeRequest…
Browse files Browse the repository at this point in the history
…-body
  • Loading branch information
Giotino committed Sep 15, 2020
2 parents 996cbe7 + 38ba09c commit c7f81e4
Show file tree
Hide file tree
Showing 20 changed files with 359 additions and 187 deletions.
42 changes: 37 additions & 5 deletions .travis.yml
@@ -1,8 +1,40 @@
language: node_js
node_js:
- '14.9.0'
- '13'
- '12'
- '10'

after_success:
- './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls'

jobs:
include:
- os: linux
dist: focal
node_js: '14'
- os: linux
dist: focal
node_js: '12'
- os: linux
dist: focal
node_js: '10'
- os: linux
dist: bionic
node_js: '14'
- os: linux
dist: bionic
node_js: '12'
- os: linux
dist: bionic
node_js: '10'
- os: windows
node_js: '14'
- os: windows
node_js: '12'
- os: windows
node_js: '10'
- os: osx
osx_image: xcode12
node_js: '14'
- os: osx
osx_image: xcode12
node_js: '12'
- os: osx
osx_image: xcode12
node_js: '10'
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "got",
"version": "11.6.1",
"version": "11.6.2",
"description": "Human-friendly and powerful HTTP request library for Node.js",
"license": "MIT",
"repository": "sindresorhus/got",
Expand Down
60 changes: 59 additions & 1 deletion readme.md
Expand Up @@ -321,6 +321,8 @@ const body = await got(url).json();
const body = await got(url, {responseType: 'json', resolveBodyOnly: true});
```

**Note:** `buffer` will return the raw body buffer. Modifying it will also alter the result of `promise.text()` and `promise.json()`. Before overwritting the buffer, please copy it first via `Buffer.from(buffer)`. See https://github.com/nodejs/node/issues/27080

###### parseJson

Type: `(text: string) => unknown`\
Expand Down Expand Up @@ -1022,7 +1024,24 @@ Type: `string`

The passphrase to decrypt the `options.https.key` (if different keys have different passphrases refer to `options.https.key` documentation).

##### Examples for `https.key`, `https.certificate` and `https.passphrase`
##### https.pfx

Type: `string | Buffer | Array<string | Buffer | object>`

[PFX or PKCS12](https://en.wikipedia.org/wiki/PKCS_12) encoded private key and certificate chain. Using `options.https.pfx` is an alternative to providing `options.https.key` and `options.https.certificate` individually. A PFX is usually encrypted, and if it is, `options.https.passphrase` will be used to decrypt it.

Multiple PFX's can be be provided as an array of unencrypted buffers or an array of objects like:

```ts
{
buffer: string | Buffer,
passphrase?: string
}
```

This object form can only occur in an array. If the provided buffers are encrypted, `object.passphrase` can be used to decrypt them. If `object.passphrase` is not provided, `options.https.passphrase` will be used for decryption.

##### Examples for `https.key`, `https.certificate`, `https.passphrase`, and `https.pfx`

```js
// Single key with certificate
Expand Down Expand Up @@ -1069,6 +1088,45 @@ got('https://example.com', {
]
}
});

// Single encrypted PFX with passphrase
got('https://example.com', {
https: {
pfx: fs.readFileSync('./fake.pfx'),
passphrase: 'passphrase'
}
});

// Multiple encrypted PFX's with different passphrases
got('https://example.com', {
https: {
pfx: [
{
buffer: fs.readFileSync('./key1.pfx'),
passphrase: 'passphrase1'
},
{
buffer: fs.readFileSync('./key2.pfx'),
passphrase: 'passphrase2'
}
]
}
});

// Multiple encrypted PFX's with single passphrase
got('https://example.com', {
https: {
passphrase: 'passphrase',
pfx: [
{
buffer: fs.readFileSync('./key1.pfx')
},
{
buffer: fs.readFileSync('./key2.pfx')
}
]
}
});
```

##### https.rejectUnauthorized
Expand Down
2 changes: 1 addition & 1 deletion source/as-promise/parse-body.ts
Expand Up @@ -18,7 +18,7 @@ const parseBody = (response: Response, responseType: ResponseType, parseJson: Pa
}

if (responseType === 'buffer') {
return Buffer.from(rawBody);
return rawBody;
}

throw new ParseError({
Expand Down
22 changes: 20 additions & 2 deletions source/core/index.ts
Expand Up @@ -858,6 +858,7 @@ export interface HTTPSOptions {
The passphrase to decrypt the `options.https.key` (if different keys have different passphrases refer to `options.https.key` documentation).
*/
passphrase?: SecureContextOptions['passphrase'];
pfx?: SecureContextOptions['pfx'];
}

interface NormalizedPlainOptions extends PlainOptions {
Expand Down Expand Up @@ -1539,6 +1540,7 @@ export default class Request extends Duplex implements RequestEvents<Request> {
assert.any([is.string, is.object, is.array, is.undefined], options.https.key);
assert.any([is.string, is.object, is.array, is.undefined], options.https.certificate);
assert.any([is.string, is.undefined], options.https.passphrase);
assert.any([is.string, is.buffer, is.array, is.undefined], options.https.pfx);
}

assert.any([is.object, is.undefined], options.cacheOptions);
Expand Down Expand Up @@ -1833,6 +1835,10 @@ export default class Request extends Duplex implements RequestEvents<Request> {
deprecationWarning('"options.passphrase" was never documented, please use "options.https.passphrase"');
}

if ('pfx' in options) {
deprecationWarning('"options.pfx" was never documented, please use "options.https.pfx"');
}

// Other options
if ('followRedirects' in options) {
throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.');
Expand Down Expand Up @@ -2394,6 +2400,10 @@ export default class Request extends Duplex implements RequestEvents<Request> {
if (options.https.passphrase) {
requestOptions.passphrase = options.https.passphrase;
}

if (options.https.pfx) {
requestOptions.pfx = options.https.pfx;
}
}

try {
Expand Down Expand Up @@ -2434,6 +2444,10 @@ export default class Request extends Duplex implements RequestEvents<Request> {
if (options.https.passphrase) {
delete requestOptions.passphrase;
}

if (options.https.pfx) {
delete requestOptions.pfx;
}
}

if (isClientRequest(requestOrResponse)) {
Expand Down Expand Up @@ -2496,9 +2510,8 @@ export default class Request extends Duplex implements RequestEvents<Request> {

try {
response.rawBody = await getBuffer(response);
response.body = response.rawBody.toString();
} catch {}

response.body = response.rawBody.toString();
}

if (this.listenerCount('retry') !== 0) {
Expand Down Expand Up @@ -2608,6 +2621,11 @@ export default class Request extends Duplex implements RequestEvents<Request> {
}

_writeRequest(chunk: any, encoding: BufferEncoding | undefined, callback: (error?: Error | null) => void): void {
if (this[kRequest]!.destroyed) {
// Probably the `ClientRequest` instance will throw
return;
}

this._progressCallbacks.push((): void => {
this[kUploadedSize] += Buffer.byteLength(chunk, encoding);

Expand Down
137 changes: 67 additions & 70 deletions test/agent.ts
@@ -1,35 +1,9 @@
import {Agent as HttpAgent} from 'http';
import {Agent as HttpsAgent} from 'https';
import {Socket} from 'net';
import {TLSSocket} from 'tls';
import test, {Constructor} from 'ava';
import sinon = require('sinon');
import {ExtendedTestServer} from './helpers/types';
import withServer from './helpers/with-server';

const prepareServer = (server: ExtendedTestServer): void => {
server.get('/', (request, response) => {
if (request.socket instanceof TLSSocket) {
response.end('https');
} else {
response.end('http');
}
});

server.get('/httpsToHttp', (_request, response) => {
response.writeHead(302, {
location: server.url
});
response.end();
});

server.get('/httpToHttps', (_request, response) => {
response.writeHead(302, {
location: server.sslUrl
});
response.end();
});
};
import withServer, {withHttpsServer} from './helpers/with-server';

const createAgentSpy = <T extends HttpsAgent>(AgentClass: Constructor): {agent: T; spy: sinon.SinonSpy} => {
const agent: T = new AgentClass({keepAlive: true});
Expand Down Expand Up @@ -59,14 +33,14 @@ test('non-object agent option works with http', withServer, async (t, server, go
agent.destroy();
});

test('non-object agent option works with https', withServer, async (t, server, got) => {
test('non-object agent option works with https', withHttpsServer(), async (t, server, got) => {
server.get('/', (_request, response) => {
response.end('ok');
});

const {agent, spy} = createAgentSpy(HttpsAgent);

t.truthy((await got.secure({
t.truthy((await got({
https: {
rejectUnauthorized: false
},
Expand All @@ -80,53 +54,79 @@ test('non-object agent option works with https', withServer, async (t, server, g
agent.destroy();
});

test('redirects from http to https work with an agent object', withServer, async (t, server, got) => {
prepareServer(server);
test('redirects from http to https work with an agent object', withServer, async (t, serverHttp) => {
await withHttpsServer()(t, async (t, serverHttps, got) => {
serverHttp.get('/', (_request, response) => {
response.end('http');
});

const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy<HttpsAgent>(HttpsAgent);
serverHttps.get('/', (_request, response) => {
response.end('https');
});

t.truthy((await got('httpToHttps', {
https: {
rejectUnauthorized: false
},
agent: {
http: httpAgent,
https: httpsAgent
}
})).body);
t.true(httpSpy.calledOnce);
t.true(httpsSpy.calledOnce);
serverHttp.get('/httpToHttps', (_request, response) => {
response.writeHead(302, {
location: serverHttps.url
});
response.end();
});

// Make sure to close all open sockets
httpAgent.destroy();
httpsAgent.destroy();
const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy<HttpsAgent>(HttpsAgent);

t.truthy((await got('httpToHttps', {
prefixUrl: serverHttp.url,
agent: {
http: httpAgent,
https: httpsAgent
}
})).body);
t.true(httpSpy.calledOnce);
t.true(httpsSpy.calledOnce);

// Make sure to close all open sockets
httpAgent.destroy();
httpsAgent.destroy();
});
});

test('redirects from https to http work with an agent object', withServer, async (t, server, got) => {
prepareServer(server);
test('redirects from https to http work with an agent object', withHttpsServer(), async (t, serverHttps, got) => {
await withServer(t, async (t, serverHttp) => {
serverHttp.get('/', (_request, response) => {
response.end('http');
});

const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent);
serverHttps.get('/', (_request, response) => {
response.end('https');
});

t.truthy((await got.secure('httpsToHttp', {
https: {
rejectUnauthorized: false
},
agent: {
http: httpAgent,
https: httpsAgent
}
})).body);
t.true(httpSpy.calledOnce);
t.true(httpsSpy.calledOnce);
serverHttps.get('/httpsToHttp', (_request, response) => {
response.writeHead(302, {
location: serverHttp.url
});
response.end();
});

// Make sure to close all open sockets
httpAgent.destroy();
httpsAgent.destroy();
const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent);
const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent);

t.truthy((await got('httpsToHttp', {
prefixUrl: serverHttps.url,
agent: {
http: httpAgent,
https: httpsAgent
}
})).body);
t.true(httpSpy.calledOnce);
t.true(httpsSpy.calledOnce);

// Make sure to close all open sockets
httpAgent.destroy();
httpsAgent.destroy();
});
});

test('socket connect listener cleaned up after request', withServer, async (t, server, got) => {
test('socket connect listener cleaned up after request', withHttpsServer(), async (t, server, got) => {
server.get('/', (_request, response) => {
response.end('ok');
});
Expand All @@ -136,10 +136,7 @@ test('socket connect listener cleaned up after request', withServer, async (t, s
// Make sure there are no memory leaks when reusing keep-alive sockets
for (let i = 0; i < 20; i++) {
// eslint-disable-next-line no-await-in-loop
await got.secure({
https: {
rejectUnauthorized: false
},
await got({
agent: {
https: agent
}
Expand Down

0 comments on commit c7f81e4

Please sign in to comment.