@datanimbus/postman-request 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,192 @@
1
+ const { EventEmitter } = require('events')
2
+ const { Http2Request: HTTP2Request } = require('../../lib/http2/request')
3
+ const { globalAgent } = require('./agent')
4
+ const { validateRequestHeaders } = require('./headerValidations')
5
+
6
+ const kJobs = Symbol('kJobs')
7
+
8
+ class MultiProtocolRequest extends EventEmitter {
9
+ constructor (options) {
10
+ super()
11
+ this[kJobs] = []
12
+ this.options = options
13
+ this.options.host = options.hostname || options.host || 'localhost'
14
+
15
+ const agent = options.agent || globalAgent
16
+ // Request agent to perform alpn and return either an http agent or https agent
17
+ // Pass the request to the agent, the agent then calls the callback with http or http2 argument based on the result
18
+ // of alpn negotiation
19
+ agent.createConnection(this, options, (err, proto, req) => {
20
+ if (err) {
21
+ this.emit('error', err)
22
+ return
23
+ }
24
+ if (proto === 'http2') {
25
+ this.onHttp2(req)
26
+ }
27
+ if (proto === 'http1') {
28
+ this.onHttp(req)
29
+ }
30
+ }, (socket) => {
31
+ // Need to register callback after this tick, after the on socket handlers have been registered.
32
+ // Node also does something similar when emitting the socket event.
33
+ process.nextTick(() => this.emit('socket', socket))
34
+ this.socket = socket
35
+ })
36
+ }
37
+
38
+ onHttp2 (connection) {
39
+ const options = {
40
+ ...this.options,
41
+ agent: {
42
+ createConnection: () => connection
43
+ }
44
+ }
45
+
46
+ let req
47
+ try {
48
+ req = new HTTP2Request(options)
49
+ } catch (e) {
50
+ this.emit('error', e)
51
+ return
52
+ }
53
+ this.registerCallbacks(req)
54
+ this._req = req
55
+ this.processQueuedOpens()
56
+ }
57
+
58
+ onHttp (req) {
59
+ this.registerCallbacks(req)
60
+ this._req = req
61
+ this.processQueuedOpens()
62
+ }
63
+
64
+ registerCallbacks (ob) {
65
+ ob.on('drain', (...args) => this.emit('drain', ...args))
66
+ ob.on('end', (...args) => this.emit('end', ...args))
67
+ ob.on('close', (...args) => this.emit('close', ...args))
68
+ ob.on('response', (...args) => this.emit('response', ...args))
69
+ ob.on('error', (...args) => this.emit('error', ...args))
70
+ }
71
+
72
+ processQueuedOpens () {
73
+ this[kJobs].forEach((action) => {
74
+ action()
75
+ })
76
+ this[kJobs] = []
77
+ }
78
+
79
+ write (data) {
80
+ const action = () => this._req.write(data)
81
+ if (this._req) {
82
+ action()
83
+ return true
84
+ }
85
+ this[kJobs].push(action)
86
+ return true
87
+ }
88
+
89
+ end (data) {
90
+ const action = () => {
91
+ this._req.end(data)
92
+ }
93
+ if (this._req) {
94
+ action()
95
+ return this
96
+ }
97
+ this[kJobs].push(action)
98
+ return this
99
+ }
100
+
101
+ setDefaultEncoding (encoding) {
102
+ const action = () => this._req.setDefaultEncoding(encoding)
103
+ if (this._req) {
104
+ action()
105
+ return this
106
+ }
107
+
108
+ this[kJobs].push(action)
109
+ return this
110
+ }
111
+
112
+ get _header () {
113
+ if (this._req && this._req._header) {
114
+ return this._req._header
115
+ }
116
+ return new Promise((resolve) => {
117
+ const action = () => resolve(this._req._header)
118
+ this[kJobs].push(action)
119
+ })
120
+ }
121
+
122
+ pipe (destination, options) {
123
+ const action = () => this._req.pipe(destination, options)
124
+ if (this._req) {
125
+ action()
126
+ return destination
127
+ }
128
+ this[kJobs].push(action)
129
+ return destination
130
+ }
131
+
132
+ setTimeout (timeout, callback) {
133
+ const action = () => this._req.setTimeout(timeout, callback)
134
+ if (this._req) {
135
+ action()
136
+ return this
137
+ }
138
+ this[kJobs].push(action)
139
+ return this
140
+ }
141
+
142
+ abort () {
143
+ const action = () => this._req.abort()
144
+ if (this._req) {
145
+ action()
146
+ return this
147
+ }
148
+ this[kJobs].push(action)
149
+ return this
150
+ }
151
+
152
+ setHeader (name, value) {
153
+ const action = () => this._req.setHeader(name, value)
154
+ if (this._req) {
155
+ action()
156
+ return this
157
+ }
158
+ this[kJobs].push(action)
159
+ return this
160
+ }
161
+
162
+ removeHeader (name) {
163
+ const action = () => this._req.removeHeader(name)
164
+ if (this._req) {
165
+ action()
166
+ return this
167
+ }
168
+ this[kJobs].push(action)
169
+ return this
170
+ }
171
+ }
172
+
173
+ function request (options) {
174
+ // request was received here, that means protocol is auto, that means priority order is http2, http
175
+ // There can be 2 cases
176
+
177
+ // 1. We have performed ALPN negotiation before for this host/port with the same agent options
178
+ // 2. We need to perform ALPN negotiation, add the socket used to perform negotiation to the appropriate agent
179
+ // 2.1 Add the agent to the pool if it didn't already exist
180
+
181
+ // HTTP/2 internal implementation sucks. In case of an invalid HTTP/2 header, it destroys the entire session and
182
+ // emits an error asynchronously, instead of throwing it synchronously. Hence, it makes more sense to perform all
183
+ // validations before sending the request.
184
+ validateRequestHeaders(options.headers)
185
+
186
+ return new MultiProtocolRequest(options)
187
+ }
188
+
189
+ module.exports = {
190
+ request,
191
+ MultiProtocolRequest
192
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+ * This function has been referenced from Node.js HTTPS Agent implementation
3
+ * Ref: v20.15.0 https://github.com/nodejs/node/blob/6bf148e12b00a3ec596f4c123ec35445a48ab209/lib/https.js
4
+ */
5
+ function getName (options) {
6
+ let name = options.host || 'localhost'
7
+
8
+ name += ':'
9
+ if (options.port) { name += options.port }
10
+
11
+ name += ':'
12
+ if (options.localAddress) { name += options.localAddress }
13
+ if (options.socketPath) { name += `:${options.socketPath}` }
14
+
15
+ name += ':'
16
+ if (options.ca) { name += options.ca }
17
+
18
+ name += ':'
19
+ if (options.extraCA) { name += options.extraCA }
20
+
21
+ name += ':'
22
+ if (options.cert) { name += options.cert }
23
+
24
+ name += ':'
25
+ if (options.clientCertEngine) { name += options.clientCertEngine }
26
+
27
+ name += ':'
28
+ if (options.ciphers) { name += options.ciphers }
29
+
30
+ name += ':'
31
+ if (options.key) { name += options.key }
32
+
33
+ name += ':'
34
+ if (options.pfx) { name += options.pfx }
35
+
36
+ name += ':'
37
+ if (options.rejectUnauthorized !== undefined) { name += options.rejectUnauthorized }
38
+
39
+ name += ':'
40
+ if (options.servername && options.servername !== options.host) { name += options.servername }
41
+
42
+ name += ':'
43
+ if (options.minVersion) { name += options.minVersion }
44
+
45
+ name += ':'
46
+ if (options.maxVersion) { name += options.maxVersion }
47
+
48
+ name += ':'
49
+ if (options.secureProtocol) { name += options.secureProtocol }
50
+
51
+ name += ':'
52
+ if (options.crl) { name += options.crl }
53
+
54
+ name += ':'
55
+ if (options.honorCipherOrder !== undefined) { name += options.honorCipherOrder }
56
+
57
+ name += ':'
58
+ if (options.ecdhCurve) { name += options.ecdhCurve }
59
+
60
+ name += ':'
61
+ if (options.dhparam) { name += options.dhparam }
62
+
63
+ name += ':'
64
+ if (options.secureOptions !== undefined) { name += options.secureOptions }
65
+
66
+ name += ':'
67
+ if (options.sessionIdContext) { name += options.sessionIdContext }
68
+
69
+ name += ':'
70
+ if (options.sigalgs) { name += JSON.stringify(options.sigalgs) }
71
+
72
+ name += ':'
73
+ if (options.privateKeyIdentifier) { name += options.privateKeyIdentifier }
74
+
75
+ name += ':'
76
+ if (options.privateKeyEngine) { name += options.privateKeyEngine }
77
+
78
+ // Create new connection since previous connection cannot be reused since it will not emit secureConnect event which will not set the session data
79
+ name += ':' + Boolean(options.verbose)
80
+
81
+ return name
82
+ }
83
+
84
+ module.exports = {
85
+ getName
86
+ }
package/lib/cookies.js ADDED
@@ -0,0 +1,20 @@
1
+ 'use strict'
2
+
3
+ var tough = require('@postman/tough-cookie')
4
+
5
+ var Cookie = tough.Cookie
6
+ var CookieJar = tough.CookieJar
7
+
8
+ exports.parse = function (str) {
9
+ if (str && str.uri) {
10
+ str = str.uri
11
+ }
12
+ if (typeof str !== 'string') {
13
+ throw new Error('The cookie function only accepts STRING as param')
14
+ }
15
+ return Cookie.parse(str, {loose: true})
16
+ }
17
+
18
+ exports.jar = function (store) {
19
+ return new CookieJar(store, {looseMode: true})
20
+ }
@@ -0,0 +1,79 @@
1
+ 'use strict'
2
+
3
+ function formatHostname (hostname) {
4
+ // canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
5
+ return hostname.replace(/^\.*/, '.').toLowerCase()
6
+ }
7
+
8
+ function parseNoProxyZone (zone) {
9
+ zone = zone.trim().toLowerCase()
10
+
11
+ var zoneParts = zone.split(':', 2)
12
+ var zoneHost = formatHostname(zoneParts[0])
13
+ var zonePort = zoneParts[1]
14
+ var hasPort = zone.indexOf(':') > -1
15
+
16
+ return {hostname: zoneHost, port: zonePort, hasPort: hasPort}
17
+ }
18
+
19
+ function uriInNoProxy (uri, noProxy) {
20
+ var port = uri.port || (uri.protocol === 'https:' ? '443' : '80')
21
+ var hostname = formatHostname(uri.hostname)
22
+ var noProxyList = noProxy.split(',')
23
+
24
+ // iterate through the noProxyList until it finds a match.
25
+ return noProxyList.map(parseNoProxyZone).some(function (noProxyZone) {
26
+ var isMatchedAt = hostname.indexOf(noProxyZone.hostname)
27
+ var hostnameMatched = (
28
+ isMatchedAt > -1 &&
29
+ (isMatchedAt === hostname.length - noProxyZone.hostname.length)
30
+ )
31
+
32
+ if (noProxyZone.hasPort) {
33
+ return (port === noProxyZone.port) && hostnameMatched
34
+ }
35
+
36
+ return hostnameMatched
37
+ })
38
+ }
39
+
40
+ function getProxyFromURI (uri) {
41
+ // Decide the proper request proxy to use based on the request URI object and the
42
+ // environmental variables (NO_PROXY, HTTP_PROXY, etc.)
43
+ // respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html)
44
+
45
+ var noProxy = process.env.NO_PROXY || process.env.no_proxy || ''
46
+
47
+ // if the noProxy is a wildcard then return null
48
+
49
+ if (noProxy === '*') {
50
+ return null
51
+ }
52
+
53
+ // if the noProxy is not empty and the uri is found return null
54
+
55
+ if (noProxy !== '' && uriInNoProxy(uri, noProxy)) {
56
+ return null
57
+ }
58
+
59
+ // Check for HTTP or HTTPS Proxy in environment Else default to null
60
+
61
+ if (uri.protocol === 'http:') {
62
+ return process.env.HTTP_PROXY ||
63
+ process.env.http_proxy || null
64
+ }
65
+
66
+ if (uri.protocol === 'https:') {
67
+ return process.env.HTTPS_PROXY ||
68
+ process.env.https_proxy ||
69
+ process.env.HTTP_PROXY ||
70
+ process.env.http_proxy || null
71
+ }
72
+
73
+ // if none of that works, return null
74
+ // (What uri protocol are you using then?)
75
+
76
+ return null
77
+ }
78
+
79
+ module.exports = getProxyFromURI
package/lib/har.js ADDED
@@ -0,0 +1,200 @@
1
+ 'use strict'
2
+
3
+ var fs = require('fs')
4
+ var qs = require('querystring')
5
+ var extend = require('extend')
6
+
7
+ function Har (request) {
8
+ this.request = request
9
+ }
10
+
11
+ Har.prototype.reducer = function (obj, pair) {
12
+ // new property ?
13
+ if (obj[pair.name] === undefined) {
14
+ obj[pair.name] = pair.value
15
+ return obj
16
+ }
17
+
18
+ // existing? convert to array
19
+ var arr = [
20
+ obj[pair.name],
21
+ pair.value
22
+ ]
23
+
24
+ obj[pair.name] = arr
25
+
26
+ return obj
27
+ }
28
+
29
+ Har.prototype.prep = function (data) {
30
+ // construct utility properties
31
+ data.queryObj = {}
32
+ data.headersObj = {}
33
+ data.postData.jsonObj = false
34
+ data.postData.paramsObj = false
35
+
36
+ // construct query objects
37
+ if (data.queryString && data.queryString.length) {
38
+ data.queryObj = data.queryString.reduce(this.reducer, {})
39
+ }
40
+
41
+ // construct headers objects
42
+ if (data.headers && data.headers.length) {
43
+ // loweCase header keys
44
+ data.headersObj = data.headers.reduceRight(function (headers, header) {
45
+ headers[header.name] = header.value
46
+ return headers
47
+ }, {})
48
+ }
49
+
50
+ // construct Cookie header
51
+ if (data.cookies && data.cookies.length) {
52
+ var cookies = data.cookies.map(function (cookie) {
53
+ return cookie.name + '=' + cookie.value
54
+ })
55
+
56
+ if (cookies.length) {
57
+ data.headersObj.cookie = cookies.join('; ')
58
+ }
59
+ }
60
+
61
+ // prep body
62
+ function some (arr) {
63
+ return arr.some(function (type) {
64
+ return data.postData.mimeType.indexOf(type) === 0
65
+ })
66
+ }
67
+
68
+ if (some([
69
+ 'multipart/mixed',
70
+ 'multipart/related',
71
+ 'multipart/form-data',
72
+ 'multipart/alternative'])) {
73
+ // reset values
74
+ data.postData.mimeType = 'multipart/form-data'
75
+ } else if (some([
76
+ 'application/x-www-form-urlencoded'])) {
77
+ if (!data.postData.params) {
78
+ data.postData.text = ''
79
+ } else {
80
+ data.postData.paramsObj = data.postData.params.reduce(this.reducer, {})
81
+
82
+ // always overwrite
83
+ data.postData.text = qs.stringify(data.postData.paramsObj)
84
+ }
85
+ } else if (some([
86
+ 'text/json',
87
+ 'text/x-json',
88
+ 'application/json',
89
+ 'application/x-json'])) {
90
+ data.postData.mimeType = 'application/json'
91
+
92
+ if (data.postData.text) {
93
+ try {
94
+ data.postData.jsonObj = JSON.parse(data.postData.text)
95
+ } catch (e) {
96
+ this.request.debug(e)
97
+
98
+ // force back to text/plain
99
+ data.postData.mimeType = 'text/plain'
100
+ }
101
+ }
102
+ }
103
+
104
+ return data
105
+ }
106
+
107
+ Har.prototype.options = function (options) {
108
+ // skip if no har property defined
109
+ if (!options.har) {
110
+ return options
111
+ }
112
+
113
+ var har = {}
114
+ extend(har, options.har)
115
+
116
+ // only process the first entry
117
+ if (har.log && har.log.entries) {
118
+ har = har.log.entries[0]
119
+ }
120
+
121
+ // add optional properties to make validation successful
122
+ har.url = har.url || options.url || options.uri || options.baseUrl || '/'
123
+ har.httpVersion = har.httpVersion || 'HTTP/1.1'
124
+ har.queryString = har.queryString || []
125
+ har.headers = har.headers || []
126
+ har.cookies = har.cookies || []
127
+ har.postData = har.postData || {}
128
+ har.postData.mimeType = har.postData.mimeType || 'application/octet-stream'
129
+
130
+ har.bodySize = 0
131
+ har.headersSize = 0
132
+ har.postData.size = 0
133
+
134
+ // clean up and get some utility properties
135
+ var req = this.prep(har)
136
+
137
+ // construct new options
138
+ if (req.url) {
139
+ options.url = req.url
140
+ }
141
+
142
+ if (req.method) {
143
+ options.method = req.method
144
+ }
145
+
146
+ if (Object.keys(req.queryObj).length) {
147
+ options.qs = req.queryObj
148
+ }
149
+
150
+ if (Object.keys(req.headersObj).length) {
151
+ options.headers = req.headersObj
152
+ }
153
+
154
+ function test (type) {
155
+ return req.postData.mimeType.indexOf(type) === 0
156
+ }
157
+ if (test('application/x-www-form-urlencoded')) {
158
+ options.form = req.postData.paramsObj
159
+ } else if (test('application/json')) {
160
+ if (req.postData.jsonObj) {
161
+ options.body = req.postData.jsonObj
162
+ options.json = true
163
+ }
164
+ } else if (test('multipart/form-data')) {
165
+ options.formData = {}
166
+
167
+ req.postData.params.forEach(function (param) {
168
+ var attachment = {}
169
+
170
+ if (!param.fileName && !param.contentType) {
171
+ options.formData[param.name] = param.value
172
+ return
173
+ }
174
+
175
+ // attempt to read from disk!
176
+ if (param.fileName && !param.value) {
177
+ attachment.value = fs.createReadStream(param.fileName)
178
+ } else if (param.value) {
179
+ attachment.value = param.value
180
+ }
181
+
182
+ if (param.fileName) {
183
+ attachment.options = {
184
+ filename: param.fileName,
185
+ contentType: param.contentType ? param.contentType : null
186
+ }
187
+ }
188
+
189
+ options.formData[param.name] = attachment
190
+ })
191
+ } else {
192
+ if (req.postData.text) {
193
+ options.body = req.postData.text
194
+ }
195
+ }
196
+
197
+ return options
198
+ }
199
+
200
+ exports.Har = Har
package/lib/hawk.js ADDED
@@ -0,0 +1,89 @@
1
+ 'use strict'
2
+
3
+ var crypto = require('crypto')
4
+
5
+ function randomString (size) {
6
+ var bits = (size + 1) * 6
7
+ var buffer = crypto.randomBytes(Math.ceil(bits / 8))
8
+ var string = buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
9
+ return string.slice(0, size)
10
+ }
11
+
12
+ function calculatePayloadHash (payload, algorithm, contentType) {
13
+ var hash = crypto.createHash(algorithm)
14
+ hash.update('hawk.1.payload\n')
15
+ hash.update((contentType ? contentType.split(';')[0].trim().toLowerCase() : '') + '\n')
16
+ hash.update(payload || '')
17
+ hash.update('\n')
18
+ return hash.digest('base64')
19
+ }
20
+
21
+ exports.calculateMac = function (credentials, opts) {
22
+ var normalized = 'hawk.1.header\n' +
23
+ opts.ts + '\n' +
24
+ opts.nonce + '\n' +
25
+ (opts.method || '').toUpperCase() + '\n' +
26
+ opts.resource + '\n' +
27
+ opts.host.toLowerCase() + '\n' +
28
+ opts.port + '\n' +
29
+ (opts.hash || '') + '\n'
30
+
31
+ if (opts.ext) {
32
+ normalized = normalized + opts.ext.replace('\\', '\\\\').replace('\n', '\\n')
33
+ }
34
+
35
+ normalized = normalized + '\n'
36
+
37
+ if (opts.app) {
38
+ normalized = normalized + opts.app + '\n' + (opts.dlg || '') + '\n'
39
+ }
40
+
41
+ var hmac = crypto.createHmac(credentials.algorithm, credentials.key).update(normalized)
42
+ var digest = hmac.digest('base64')
43
+ return digest
44
+ }
45
+
46
+ exports.header = function (uri, method, opts) {
47
+ var timestamp = opts.timestamp || Math.floor((Date.now() + (opts.localtimeOffsetMsec || 0)) / 1000)
48
+ var credentials = opts.credentials
49
+ if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) {
50
+ return ''
51
+ }
52
+
53
+ if (['sha256'].indexOf(credentials.algorithm) === -1) {
54
+ return ''
55
+ }
56
+
57
+ var artifacts = {
58
+ ts: timestamp,
59
+ nonce: opts.nonce || randomString(6),
60
+ method: method,
61
+ resource: uri.pathname + (uri.search || ''),
62
+ host: uri.hostname,
63
+ port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
64
+ hash: opts.hash,
65
+ ext: opts.ext,
66
+ app: opts.app,
67
+ dlg: opts.dlg
68
+ }
69
+
70
+ if (!artifacts.hash && (opts.payload || opts.payload === '')) {
71
+ artifacts.hash = calculatePayloadHash(opts.payload, credentials.algorithm, opts.contentType)
72
+ }
73
+
74
+ var mac = exports.calculateMac(credentials, artifacts)
75
+
76
+ var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''
77
+ var header = 'Hawk id="' + credentials.id +
78
+ '", ts="' + artifacts.ts +
79
+ '", nonce="' + artifacts.nonce +
80
+ (artifacts.hash ? '", hash="' + artifacts.hash : '') +
81
+ (hasExt ? '", ext="' + artifacts.ext.replace(/\\/g, '\\\\').replace(/"/g, '\\"') : '') +
82
+ '", mac="' + mac + '"'
83
+
84
+ if (artifacts.app) {
85
+ header = header + ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'
86
+ }
87
+
88
+ return header
89
+ }