postman-request
Advanced tools
Comparing version 2.88.1-postman.5-beta.1 to 2.88.1-postman.8-beta.1
87
index.js
@@ -17,2 +17,3 @@ // Copyright 2010-2012 Mikeal Rogers | ||
var tls = require('tls') | ||
var extend = require('extend') | ||
@@ -134,2 +135,88 @@ var cookies = require('./lib/cookies') | ||
// As of now (Node v10.x LTS), the only way to extend the well known "root" CA | ||
// is by using an environment variable called `NODE_EXTRA_CA_CERTS`. | ||
// This function enables the same functionality and provides a programmatic way | ||
// to extend the CA certificates. | ||
// Refer: https://nodejs.org/docs/latest-v10.x/api/cli.html#cli_node_extra_ca_certs_file | ||
// | ||
// @note Unlike NODE_EXTRA_CA_CERTS, this method extends the CA for every | ||
// request sent and since its an expensive operation its advised to use a | ||
// keepAlive agent(agentOptions.keepAlive: true) when this is enabled. | ||
// | ||
// Benchmarks using a local server: | ||
// NODE_EXTRA_CA_CERTS (keepAlive: false) : 422 ops/sec ±1.73% (77 runs sampled) | ||
// NODE_EXTRA_CA_CERTS (keepAlive: true) : 2,096 ops/sec ±4.23% (69 runs sampled) | ||
// | ||
// enableNodeExtraCACerts (keepAlive: false) : 331 ops/sec ±5.64% (77 runs sampled) | ||
// enableNodeExtraCACerts (keepAlive: true) : 2,045 ops/sec ±5.20% (69 runs sampled) | ||
// | ||
// @note Enabling this will override the singleton `tls.createSecureContext` method | ||
// which will be affected for every request sent (using native HTTPS etc.) on the | ||
// same process. BUT, this will only be effective when `extraCA` option is | ||
// passed to `tls.createSecureContext`, which is limited to this library. | ||
request.enableNodeExtraCACerts = function (callback) { | ||
// @note callback is optional to catch missing tls method | ||
!callback && (callback = function () {}) | ||
// bail out if already enabled | ||
if (tls.__createSecureContext) { | ||
return callback() | ||
} | ||
// enable only if `SecureContext.addCACert` is present | ||
// otherwise return callback with error. | ||
// @note try-catch is used to make sure testing this will not break | ||
// the main process due to OpenSSL error. | ||
try { | ||
var testContext = tls.createSecureContext() | ||
if (!(testContext && testContext.context && | ||
typeof testContext.context.addCACert === 'function')) { | ||
return callback(new Error('SecureContext.addCACert is not a function')) | ||
} | ||
} catch (err) { | ||
return callback(err) | ||
} | ||
// store the original tls.createSecureContext method. | ||
// used to extend existing functionality as well as restore later. | ||
tls.__createSecureContext = tls.createSecureContext | ||
// override tls.createSecureContext with extraCA support | ||
// @note if agent is keepAlive, same context will be reused. | ||
tls.createSecureContext = function () { | ||
// call original createSecureContext and store the context | ||
var secureContext = tls.__createSecureContext.apply(this, arguments) | ||
// if `extraCA` is present in options, extend CA certs | ||
// @note this request option is available here because all the | ||
// Request properties are passed to HTTPS Agent. | ||
if (arguments[0] && arguments[0].extraCA) { | ||
// extend root CA with specified CA certificates | ||
// @note `addCACert` is an undocumented API and performs an expensive operations | ||
// Refer: https://github.com/nodejs/node/blob/v10.15.1/lib/_tls_common.js#L97 | ||
secureContext.context.addCACert(arguments[0].extraCA) | ||
} | ||
return secureContext | ||
} | ||
// enabled extra CA support | ||
return callback() | ||
} | ||
// disable the extended CA certificates feature | ||
request.disableNodeExtraCACerts = function () { | ||
// bail out if not enabled | ||
if (typeof tls.__createSecureContext !== 'function') { | ||
return | ||
} | ||
// reset `tls.createSecureContext` with the original method | ||
tls.createSecureContext = tls.__createSecureContext | ||
// delete the reference of original method | ||
delete tls.__createSecureContext | ||
} | ||
// Exports | ||
@@ -136,0 +223,0 @@ |
@@ -124,22 +124,34 @@ 'use strict' | ||
// remove the host header if the response is a 307 or 308, & the host has changed. | ||
// (the new host will be populated auto-magically). | ||
// For other types of redirects, it's removed automatically (see code below) | ||
if (self.followAllRedirects && | ||
(response.statusCode === 307 || response.statusCode === 308) && | ||
request.headers && uriPrev.host !== request.uri.host) { | ||
self.redirects.push({ statusCode: response.statusCode, redirectUri: redirectTo }) | ||
// if the redirect hostname (not just port or protocol) is changed: | ||
// 1. remove host header, the new host will be populated on request.init | ||
// 2. remove authorization header, avoid authentication leak | ||
// @note: This is done because of security reasons, irrespective of the | ||
// status code or request method used. | ||
if (request.headers && uriPrev.hostname !== request.uri.hostname) { | ||
request.removeHeader('host') | ||
// use followAuthorizationHeader option to retain authorization header | ||
if (!self.followAuthorizationHeader) { | ||
request.removeHeader('authorization') | ||
} | ||
} | ||
self.redirects.push({ statusCode: response.statusCode, redirectUri: redirectTo }) | ||
if (self.followAllRedirects && request.method !== 'HEAD' && | ||
response.statusCode !== 401 && response.statusCode !== 307 && response.statusCode !== 308) { | ||
request.method = self.followOriginalHttpMethod ? request.method : 'GET' | ||
} | ||
// request.method = 'GET' // Force all redirects to use GET || commented out fixes #215 | ||
delete request.src | ||
delete request.req | ||
delete request._started | ||
// if statusCode code is 401, 307 or 308: | ||
// 1. Switch request method to GET if followOriginalHttpMethod is not set | ||
// 2. Remove request body on redirect | ||
if (response.statusCode !== 401 && response.statusCode !== 307 && response.statusCode !== 308) { | ||
// force all redirects to use GET (due to legacy shenanigans) | ||
// use followOriginalHttpMethod option to avoid this | ||
// @todo: figure out why its only done for HEAD method and not for similar | ||
// request methods like OPTIONS or CONNECT. | ||
if (!self.followOriginalHttpMethod && request.method !== 'HEAD') { | ||
request.method = 'GET' | ||
} | ||
// Remove parameters from the previous response, unless this is the second request | ||
@@ -153,9 +165,2 @@ // for a server that requires digest authentication. | ||
request.removeHeader('content-length') | ||
if (!self.followAuthorizationHeader && request.uri.hostname !== request.originalHost.split(':')[0]) { | ||
// Remove authorization if changing hostnames (but not if just | ||
// changing ports or protocols). This matches the behavior of curl: | ||
// https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710 | ||
// Change this behavior using the followAuthorizationHeader option | ||
request.removeHeader('authorization') | ||
} | ||
} | ||
@@ -162,0 +167,0 @@ } else if (request.formData && |
@@ -10,3 +10,3 @@ { | ||
], | ||
"version": "2.88.1-postman.5-beta.1", | ||
"version": "2.88.1-postman.8-beta.1", | ||
"repository": { | ||
@@ -13,0 +13,0 @@ "type": "git", |
122
README.md
@@ -16,2 +16,7 @@ | ||
- Respect form-data fields ordering | ||
- Fixed authentication leak in 307 and 308 redirects | ||
- Added `secureConnect` to timings and `secureHandshake` to timingPhases | ||
- Fixed `Request~getNewAgent` to account for `passphrase` while generating poolKey | ||
- Added support for extending the root CA certificates | ||
- Added `verbose` mode to bubble up low-level request-response information | ||
@@ -686,3 +691,3 @@ ## Super simple to use | ||
1. its own public key, which is signed by: | ||
2. an intermediate "Corp Issuing Server", that is in turn signed by: | ||
2. an intermediate "Corp Issuing Server", that is in turn signed by: | ||
3. a root CA "Corp Root CA"; | ||
@@ -704,2 +709,111 @@ | ||
### Using `options.verbose` | ||
Using this option the debug object holds low level request response information like remote address, negotiated ciphers etc. Example debug object: | ||
```js | ||
request({url: 'https://www.google.com', verbose: true}, function (error, response, body, debug) { | ||
// debug: | ||
/* | ||
[ | ||
{ | ||
"request": { | ||
"method": "GET", | ||
"href": "https://www.google.com/", | ||
"httpVersion": "1.1" | ||
}, | ||
"session": { | ||
"id": "9a1ac0d7-b757-48ad-861c-d59d6af5f43f", | ||
"reused": false, | ||
"data": { | ||
"addresses": { | ||
"local": { | ||
"address": "8.8.4.4", | ||
"family": "IPv4", | ||
"port": 61632 | ||
}, | ||
"remote": { | ||
"address": "172.217.31.196", | ||
"family": "IPv4", | ||
"port": 443 | ||
} | ||
}, | ||
"tls": { | ||
"reused": false, | ||
"authorized": true, | ||
"authorizationError": null, | ||
"cipher": { | ||
"name": "ECDHE-ECDSA-AES128-GCM-SHA256", | ||
"version": "TLSv1/SSLv3" | ||
}, | ||
"protocol": "TLSv1.2", | ||
"ephemeralKeyInfo": { | ||
"type": "ECDH", | ||
"name": "X25519", | ||
"size": 253 | ||
}, | ||
"peerCertificate": { | ||
"subject": { | ||
"country": "US", | ||
"stateOrProvince": "California", | ||
"locality": "Mountain View", | ||
"organization": "Google LLC", | ||
"commonName": "www.google.com", | ||
"alternativeNames": "DNS:www.google.com" | ||
}, | ||
"issuer": { | ||
"country": "US", | ||
"organization": "Google Trust Services", | ||
"commonName": "Google Internet Authority G3" | ||
}, | ||
"validFrom": "2019-03-01T09:46:35.000Z", | ||
"validTo": "2019-05-24T09:25:00.000Z", | ||
"fingerprint": "DF:6B:95:81:C6:03:EB:ED:48:EB:6C:CF:EE:FE:E6:1F:AD:01:78:34", | ||
"serialNumber": "3A15F4C87FB4D33993D3EEB3BF4AE5E4" | ||
} | ||
} | ||
} | ||
}, | ||
"response": { | ||
"statusCode": 200, | ||
"httpVersion": "1.1" | ||
}, | ||
"timingStart": 1552908287924, | ||
"timingStartTimer": 805.690674, | ||
"timings": { | ||
"socket": 28.356426000000056, | ||
"lookup": 210.3752320000001, | ||
"connect": 224.57993499999998, | ||
"secureConnect": 292.80315800000017, | ||
"response": 380.61268100000007, | ||
"end": 401.8332560000001 | ||
} | ||
} | ||
] | ||
*/ | ||
}); | ||
``` | ||
### Extending root CAs | ||
When this feature is enabled, the root CAs can be extended using the `extraCA` option. The file should consist of one or more trusted certificates in PEM format. | ||
This is similar to [NODE_EXTRA_CA_CERTS](https://nodejs.org/api/cli.html#cli_node_extra_ca_certs_file). But, if `options.ca` is specified, those will be extended as well. | ||
```js | ||
// enable extending CAs | ||
request.enableNodeExtraCACerts(); | ||
// request with extra CA certs | ||
request.get({ | ||
url: 'https://api.some-server.com/', | ||
extraCA: fs.readFileSync('Extra CA Certificates .pem') | ||
}); | ||
// disable this feature | ||
request.disableNodeExtraCACerts() | ||
``` | ||
[back to top](#table-of-contents) | ||
@@ -869,2 +983,3 @@ | ||
- `connect`: Relative timestamp when the [`net`](https://nodejs.org/api/net.html#net_event_connect) module's `connect` event fires. This happens when the server acknowledges the TCP connection. | ||
- `secureConnect`: Relative timestamp when the [`tls`](https://nodejs.org/api/tls.html#tls_event_secureconnect) module's `secureconnect` event fires. This happens when the handshaking process for a new connection has successfully completed. | ||
- `response`: Relative timestamp when the [`http`](https://nodejs.org/api/http.html#http_event_response) module's `response` event fires. This happens when the first bytes are received from the server. | ||
@@ -875,4 +990,5 @@ - `end`: Relative timestamp when the last bytes of the response are received. | ||
- `dns`: Duration of DNS lookup (`timings.lookup` - `timings.socket`) | ||
- `tcp`: Duration of TCP connection (`timings.connect` - `timings.socket`) | ||
- `firstByte`: Duration of HTTP server response (`timings.response` - `timings.connect`) | ||
- `tcp`: Duration of TCP connection (`timings.connect` - `timings.lookup`) | ||
- `secureHandshake`: Duration of SSL handshake (`timings.secureConnect` - `timings.connect`) | ||
- `firstByte`: Duration of HTTP server response (`timings.response` - `timings.connect`|`timings.secureConnect`) | ||
- `download`: Duration of HTTP download (`timings.end` - `timings.response`) | ||
@@ -879,0 +995,0 @@ - `total`: Duration entire HTTP round-trip (`timings.end`) |
161
request.js
'use strict' | ||
var tls = require('tls') | ||
var http = require('http') | ||
@@ -11,2 +12,3 @@ var https = require('https') | ||
var aws4 = require('aws4') | ||
var uuid = require('uuid/v4') | ||
var httpSignature = require('http-signature') | ||
@@ -159,2 +161,3 @@ var mime = require('mime-types') | ||
self.writable = true | ||
self._debug = [] | ||
if (options.method) { | ||
@@ -195,2 +198,7 @@ self.explicitMethod = true | ||
// for this request (or redirect) store its debug logs in `_reqResInfo` and | ||
// store its reference in `_debug` which holds debug logs of every request | ||
self._reqResInfo = {} | ||
self._debug.push(self._reqResInfo) | ||
// additional postman feature starts | ||
@@ -245,3 +253,3 @@ // bind default events sent via options | ||
self._callback = self.callback | ||
self.callback = function () { | ||
self.callback = function (error, response, body) { | ||
if (self._callbackCalled) { | ||
@@ -251,3 +259,3 @@ return // Print a warning maybe? | ||
self._callbackCalled = true | ||
self._callback.apply(self, arguments) | ||
self._callback(error, response, body, self._debug) | ||
} | ||
@@ -469,3 +477,4 @@ self.on('error', self.callback.bind()) | ||
if (options.time) { | ||
// enable timings if verbose is true | ||
if (options.time || options.verbose) { | ||
self.timing = true | ||
@@ -477,2 +486,6 @@ | ||
if (options.verbose) { | ||
self.verbose = true | ||
} | ||
function setContentLength () { | ||
@@ -670,2 +683,5 @@ if (isTypedArray(self.body)) { | ||
} | ||
if (self.extraCA) { | ||
options.extraCA = self.extraCA | ||
} | ||
if (self.ciphers) { | ||
@@ -719,2 +735,10 @@ options.ciphers = self.ciphers | ||
// only add when NodeExtraCACerts is enabled | ||
if (tls.__createSecureContext && options.extraCA) { | ||
if (poolKey) { | ||
poolKey += ':' | ||
} | ||
poolKey += options.extraCA | ||
} | ||
if (typeof options.rejectUnauthorized !== 'undefined') { | ||
@@ -741,2 +765,9 @@ if (poolKey) { | ||
if (options.passphrase) { | ||
if (poolKey) { | ||
poolKey += ':' | ||
} | ||
poolKey += options.passphrase | ||
} | ||
if (options.ciphers) { | ||
@@ -817,2 +848,9 @@ if (poolKey) { | ||
self._reqResInfo.request = { | ||
method: self.method, | ||
href: self.uri.href, | ||
proxy: (self.proxy && { href: self.proxy.href }) || undefined, | ||
httpVersion: '1.1' | ||
} | ||
// We have a method named auth, which is completely different from the http.request | ||
@@ -869,2 +907,22 @@ // auth option. If we don't remove it, we're gonna have a bad time. | ||
self.req.on('socket', function (socket) { | ||
if (self.verbose) { | ||
// The reused socket holds all the session data which was injected in | ||
// during the first connection. This is done because events like | ||
// `lookup`, `connect` & `secureConnect` will not be triggered for a | ||
// reused socket and debug information will be lost for that request. | ||
var reusedSocket = Boolean(socket.__SESSION_ID && socket.__SESSION_DATA) | ||
if (!reusedSocket) { | ||
socket.__SESSION_ID = uuid() | ||
socket.__SESSION_DATA = {} | ||
} | ||
// @note make sure you don't serialize this object to avoid memory leak | ||
self._reqResInfo.session = { | ||
id: socket.__SESSION_ID, | ||
reused: reusedSocket, | ||
data: socket.__SESSION_DATA | ||
} | ||
} | ||
// `._connecting` was the old property which was made public in node v6.1.0 | ||
@@ -882,6 +940,81 @@ var isConnecting = socket._connecting || socket.connecting | ||
self.timings.connect = now() - self.startTimeNow | ||
if (self.verbose) { | ||
socket.__SESSION_DATA.addresses = { | ||
// local address | ||
// @note there's no `socket.localFamily` but `.address` method | ||
// returns same output as of remote. | ||
local: (typeof socket.address === 'function') && socket.address(), | ||
// remote address | ||
remote: { | ||
address: socket.remoteAddress, | ||
family: socket.remoteFamily, | ||
port: socket.remotePort | ||
} | ||
} | ||
} | ||
} | ||
var onSecureConnectTiming = function () { | ||
self.timings.secureConnect = now() - self.startTimeNow | ||
if (self.verbose) { | ||
socket.__SESSION_DATA.tls = { | ||
// true if the session was reused | ||
reused: (typeof socket.isSessionReused === 'function') && socket.isSessionReused(), | ||
// true if the peer certificate was signed by one of the CAs specified | ||
authorized: socket.authorized, | ||
// reason why the peer's certificate was not been verified | ||
authorizationError: socket.authorizationError, | ||
// negotiated cipher name | ||
cipher: (typeof socket.getCipher === 'function') && socket.getCipher(), | ||
// negotiated SSL/TLS protocol version | ||
// @note Node >= v5.7.0 | ||
protocol: (typeof socket.getProtocol === 'function') && socket.getProtocol(), | ||
// type, name, and size of parameter of an ephemeral key exchange | ||
// @note Node >= v5.0.0 | ||
ephemeralKeyInfo: (typeof socket.getEphemeralKeyInfo === 'function') && socket.getEphemeralKeyInfo() | ||
} | ||
// peer certificate information | ||
// @note if session is reused, all certificate information is | ||
// stripped from the socket (returns {}). | ||
// Refer: https://github.com/nodejs/node/issues/3940 | ||
var peerCert = (typeof socket.getPeerCertificate === 'function') && (socket.getPeerCertificate() || {}) | ||
socket.__SESSION_DATA.tls.peerCertificate = { | ||
subject: peerCert.subject && { | ||
country: peerCert.subject.C, | ||
stateOrProvince: peerCert.subject.ST, | ||
locality: peerCert.subject.L, | ||
organization: peerCert.subject.O, | ||
organizationalUnit: peerCert.subject.OU, | ||
commonName: peerCert.subject.CN, | ||
alternativeNames: peerCert.subjectaltname | ||
}, | ||
issuer: peerCert.issuer && { | ||
country: peerCert.issuer.C, | ||
stateOrProvince: peerCert.issuer.ST, | ||
locality: peerCert.issuer.L, | ||
organization: peerCert.issuer.O, | ||
organizationalUnit: peerCert.issuer.OU, | ||
commonName: peerCert.issuer.CN | ||
}, | ||
validFrom: peerCert.valid_from && new Date(peerCert.valid_from), | ||
validTo: peerCert.valid_to && new Date(peerCert.valid_to), | ||
fingerprint: peerCert.fingerprint, | ||
serialNumber: peerCert.serialNumber | ||
} | ||
} | ||
} | ||
socket.once('lookup', onLookupTiming) | ||
socket.once('connect', onConnectTiming) | ||
socket.once('secureConnect', onSecureConnectTiming) | ||
@@ -981,2 +1114,3 @@ // clean up timing event listeners if needed on error | ||
response.timingStart = self.startTime | ||
response.timingStartTimer = self.startTimeNow | ||
@@ -994,2 +1128,5 @@ // fill in the blanks for any periods that didn't trigger, such as | ||
} | ||
if (!self.timings.secureConnect && self.httpModule === https) { | ||
self.timings.secureConnect = self.timings.connect | ||
} | ||
if (!self.timings.response) { | ||
@@ -1019,3 +1156,10 @@ self.timings.response = self.timings.connect | ||
} | ||
// if secureConnect is present, add secureHandshake and update firstByte | ||
if (self.timings.secureConnect) { | ||
response.timingPhases.secureHandshake = self.timings.secureConnect - self.timings.connect | ||
response.timingPhases.firstByte = self.timings.response - self.timings.secureConnect | ||
} | ||
} | ||
debug('response end', self.uri.href, response.statusCode, response.headers) | ||
@@ -1030,2 +1174,13 @@ }) | ||
self._reqResInfo.response = { | ||
statusCode: response.statusCode, | ||
httpVersion: response.httpVersion | ||
} | ||
if (self.timing) { | ||
self._reqResInfo.timingStart = self.startTime | ||
self._reqResInfo.timingStartTimer = self.startTimeNow | ||
self._reqResInfo.timings = self.timings | ||
} | ||
self.response = response | ||
@@ -1032,0 +1187,0 @@ response.request = self |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
230918
3008
1251
5