@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.
- package/LICENSE +55 -0
- package/README.md +1315 -0
- package/index.js +166 -0
- package/lib/auth.js +167 -0
- package/lib/autohttp/agent.js +206 -0
- package/lib/autohttp/headerValidations.js +40 -0
- package/lib/autohttp/index.js +8 -0
- package/lib/autohttp/request.js +192 -0
- package/lib/autohttp/requestName.js +86 -0
- package/lib/cookies.js +20 -0
- package/lib/getProxyFromURI.js +79 -0
- package/lib/har.js +200 -0
- package/lib/hawk.js +89 -0
- package/lib/helpers.js +91 -0
- package/lib/http2/agent.js +92 -0
- package/lib/http2/index.js +8 -0
- package/lib/http2/request.js +369 -0
- package/lib/inflate.js +64 -0
- package/lib/multipart.js +112 -0
- package/lib/oauth.js +147 -0
- package/lib/querystring.js +50 -0
- package/lib/redirect.js +218 -0
- package/lib/socks.js +45 -0
- package/lib/tunnel.js +175 -0
- package/lib/url-parse.js +187 -0
- package/package.json +77 -0
- package/request.js +2098 -0
|
@@ -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
|
+
}
|