@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/index.js ADDED
@@ -0,0 +1,166 @@
1
+ // Copyright 2010-2012 Mikeal Rogers
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+ 'use strict'
16
+
17
+ var extend = require('extend')
18
+ var cookies = require('./lib/cookies')
19
+
20
+ // organize params for patch, post, put, head, del
21
+ function initParams (uri, options, callback) {
22
+ if (typeof options === 'function') {
23
+ callback = options
24
+ }
25
+
26
+ var params = {protocolVersion: 'http1'}
27
+
28
+ if (options !== null && typeof options === 'object') {
29
+ extend(params, options, {uri: uri})
30
+ } else if (typeof uri === 'string') {
31
+ extend(params, {uri: uri})
32
+ } else {
33
+ extend(params, uri)
34
+ }
35
+
36
+ params.callback = callback || params.callback
37
+
38
+ // Disable http/2 when using custom agents that don't handle different versions separately
39
+ if (params.agents && !(params.agents.http1 || params.agents.auto || params.agents.http2)) {
40
+ params.protocolVersion = 'http1'
41
+ }
42
+
43
+ // Disable http/2 when using proxy or tunnels
44
+ // TODO: Remove this when http2 supports proxy and tunneling
45
+ if (params.tunnel || params.proxy) {
46
+ params.protocolVersion = 'http1'
47
+ }
48
+
49
+ // Disable flow when running in browser
50
+ if (typeof window !== 'undefined' && window.XMLHttpRequest) {
51
+ params.protocolVersion = 'http1'
52
+ }
53
+
54
+ return params
55
+ }
56
+
57
+ function request (uri, options, callback) {
58
+ if (typeof uri === 'undefined') {
59
+ throw new Error('undefined is not a valid uri or options object.')
60
+ }
61
+
62
+ var params = initParams(uri, options, callback)
63
+
64
+ return new request.Request(params)
65
+ }
66
+
67
+ function verbFunc (verb) {
68
+ var method = verb.toUpperCase()
69
+ return function (uri, options, callback) {
70
+ var params = initParams(uri, options, callback)
71
+ params.method = method
72
+ return request(params, params.callback)
73
+ }
74
+ }
75
+
76
+ // define like this to please codeintel/intellisense IDEs
77
+ request.get = verbFunc('get')
78
+ request.head = verbFunc('head')
79
+ request.options = verbFunc('options')
80
+ request.post = verbFunc('post')
81
+ request.put = verbFunc('put')
82
+ request.patch = verbFunc('patch')
83
+ request.del = verbFunc('delete')
84
+ request['delete'] = verbFunc('delete')
85
+
86
+ request.jar = function (store) {
87
+ return cookies.jar(store)
88
+ }
89
+
90
+ request.cookie = function (str) {
91
+ return cookies.parse(str)
92
+ }
93
+
94
+ function wrapRequestMethod (method, options, requester, verb) {
95
+ return function (uri, opts, callback) {
96
+ var params = initParams(uri, opts, callback)
97
+
98
+ var target = {}
99
+ extend(true, target, options, params)
100
+
101
+ target.pool = params.pool || options.pool
102
+
103
+ if (verb) {
104
+ target.method = verb.toUpperCase()
105
+ }
106
+
107
+ if (typeof requester === 'function') {
108
+ method = requester
109
+ }
110
+
111
+ return method(target, target.callback)
112
+ }
113
+ }
114
+
115
+ request.defaults = function (options, requester) {
116
+ var self = this
117
+
118
+ options = options || {}
119
+
120
+ if (typeof options === 'function') {
121
+ requester = options
122
+ options = {}
123
+ }
124
+
125
+ var defaults = wrapRequestMethod(self, options, requester)
126
+
127
+ var verbs = ['get', 'head', 'post', 'put', 'patch', 'del', 'delete']
128
+ verbs.forEach(function (verb) {
129
+ defaults[verb] = wrapRequestMethod(self[verb], options, requester, verb)
130
+ })
131
+
132
+ defaults.cookie = wrapRequestMethod(self.cookie, options, requester)
133
+ defaults.jar = self.jar
134
+ defaults.defaults = self.defaults
135
+ return defaults
136
+ }
137
+
138
+ request.forever = function (agentOptions, optionsArg) {
139
+ var options = {}
140
+ if (optionsArg) {
141
+ extend(options, optionsArg)
142
+ }
143
+ if (agentOptions) {
144
+ options.agentOptions = agentOptions
145
+ }
146
+
147
+ options.forever = true
148
+ return request.defaults(options)
149
+ }
150
+
151
+ // Exports
152
+
153
+ module.exports = request
154
+ request.Request = require('./request')
155
+ request.initParams = initParams
156
+
157
+ // Backwards compatibility for request.debug
158
+ Object.defineProperty(request, 'debug', {
159
+ enumerable: true,
160
+ get: function () {
161
+ return request.Request.debug
162
+ },
163
+ set: function (debug) {
164
+ request.Request.debug = debug
165
+ }
166
+ })
package/lib/auth.js ADDED
@@ -0,0 +1,167 @@
1
+ 'use strict'
2
+
3
+ var caseless = require('caseless')
4
+ var uuid = require('uuid').v4
5
+ var helpers = require('./helpers')
6
+
7
+ var md5 = helpers.md5
8
+ var toBase64 = helpers.toBase64
9
+
10
+ function Auth (request) {
11
+ // define all public properties here
12
+ this.request = request
13
+ this.hasAuth = false
14
+ this.sentAuth = false
15
+ this.bearerToken = null
16
+ this.user = null
17
+ this.pass = null
18
+ }
19
+
20
+ Auth.prototype.basic = function (user, pass, sendImmediately) {
21
+ var self = this
22
+ if (typeof user !== 'string' || (pass !== undefined && typeof pass !== 'string')) {
23
+ self.request.emit('error', new Error('auth() received invalid user or password'))
24
+ }
25
+ self.user = user
26
+ self.pass = pass
27
+ self.hasAuth = true
28
+ var header = user + ':' + (pass || '')
29
+ if (sendImmediately || typeof sendImmediately === 'undefined') {
30
+ var authHeader = 'Basic ' + toBase64(header)
31
+ self.sentAuth = true
32
+ return authHeader
33
+ }
34
+ }
35
+
36
+ Auth.prototype.bearer = function (bearer, sendImmediately) {
37
+ var self = this
38
+ self.bearerToken = bearer
39
+ self.hasAuth = true
40
+ if (sendImmediately || typeof sendImmediately === 'undefined') {
41
+ if (typeof bearer === 'function') {
42
+ bearer = bearer()
43
+ }
44
+ var authHeader = 'Bearer ' + (bearer || '')
45
+ self.sentAuth = true
46
+ return authHeader
47
+ }
48
+ }
49
+
50
+ Auth.prototype.digest = function (method, path, authHeader) {
51
+ // TODO: More complete implementation of RFC 2617.
52
+ // - handle challenge.domain
53
+ // - support qop="auth-int" only
54
+ // - handle Authentication-Info (not necessarily?)
55
+ // - check challenge.stale (not necessarily?)
56
+ // - increase nc (not necessarily?)
57
+ // For reference:
58
+ // http://tools.ietf.org/html/rfc2617#section-3
59
+ // https://github.com/bagder/curl/blob/master/lib/http_digest.c
60
+
61
+ var self = this
62
+
63
+ var challenge = {}
64
+ var re = /([a-z0-9_-]+)=(?:"([^"]+)"|([a-z0-9_-]+))/gi
65
+ while (true) {
66
+ var match = re.exec(authHeader)
67
+ if (!match) {
68
+ break
69
+ }
70
+ challenge[match[1]] = match[2] || match[3]
71
+ }
72
+
73
+ /**
74
+ * RFC 2617: handle both MD5 and MD5-sess algorithms.
75
+ *
76
+ * If the algorithm directive's value is "MD5" or unspecified, then HA1 is
77
+ * HA1=MD5(username:realm:password)
78
+ * If the algorithm directive's value is "MD5-sess", then HA1 is
79
+ * HA1=MD5(MD5(username:realm:password):nonce:cnonce)
80
+ */
81
+ var ha1Compute = function (algorithm, user, realm, pass, nonce, cnonce) {
82
+ var ha1 = md5(user + ':' + realm + ':' + pass)
83
+ if (algorithm && algorithm.toLowerCase() === 'md5-sess') {
84
+ return md5(ha1 + ':' + nonce + ':' + cnonce)
85
+ } else {
86
+ return ha1
87
+ }
88
+ }
89
+
90
+ var qop = /(^|,)\s*auth\s*($|,)/.test(challenge.qop) && 'auth'
91
+ var nc = qop && '00000001'
92
+ var cnonce = qop && uuid().replace(/-/g, '')
93
+ var ha1 = ha1Compute(challenge.algorithm, self.user, challenge.realm, self.pass, challenge.nonce, cnonce)
94
+ var ha2 = md5(method + ':' + path)
95
+ var digestResponse = qop
96
+ ? md5(ha1 + ':' + challenge.nonce + ':' + nc + ':' + cnonce + ':' + qop + ':' + ha2)
97
+ : md5(ha1 + ':' + challenge.nonce + ':' + ha2)
98
+ var authValues = {
99
+ username: self.user,
100
+ realm: challenge.realm,
101
+ nonce: challenge.nonce,
102
+ uri: path,
103
+ qop: qop,
104
+ response: digestResponse,
105
+ nc: nc,
106
+ cnonce: cnonce,
107
+ algorithm: challenge.algorithm,
108
+ opaque: challenge.opaque
109
+ }
110
+
111
+ authHeader = []
112
+ for (var k in authValues) {
113
+ if (authValues[k]) {
114
+ if (k === 'qop' || k === 'nc' || k === 'algorithm') {
115
+ authHeader.push(k + '=' + authValues[k])
116
+ } else {
117
+ authHeader.push(k + '="' + authValues[k] + '"')
118
+ }
119
+ }
120
+ }
121
+ authHeader = 'Digest ' + authHeader.join(', ')
122
+ self.sentAuth = true
123
+ return authHeader
124
+ }
125
+
126
+ Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
127
+ var self = this
128
+ var request = self.request
129
+
130
+ var authHeader
131
+ if (bearer === undefined && user === undefined) {
132
+ self.request.emit('error', new Error('no auth mechanism defined'))
133
+ } else if (bearer !== undefined) {
134
+ authHeader = self.bearer(bearer, sendImmediately)
135
+ } else {
136
+ authHeader = self.basic(user, pass, sendImmediately)
137
+ }
138
+ if (authHeader) {
139
+ request.setHeader('Authorization', authHeader)
140
+ }
141
+ }
142
+
143
+ Auth.prototype.onResponse = function (response) {
144
+ var self = this
145
+ var request = self.request
146
+
147
+ if (!self.hasAuth || self.sentAuth) { return null }
148
+
149
+ var c = caseless(response.headers)
150
+
151
+ var authHeader = c.get('www-authenticate')
152
+ var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
153
+ request.debug('reauth', authVerb)
154
+
155
+ switch (authVerb) {
156
+ case 'basic':
157
+ return self.basic(self.user, self.pass, true)
158
+
159
+ case 'bearer':
160
+ return self.bearer(self.bearerToken, true)
161
+
162
+ case 'digest':
163
+ return self.digest(request.method, request.path, authHeader)
164
+ }
165
+ }
166
+
167
+ exports.Auth = Auth
@@ -0,0 +1,206 @@
1
+ const { Agent: Http2Agent } = require('../http2')
2
+ const https = require('https')
3
+ const tls = require('tls')
4
+ const { EventEmitter } = require('events')
5
+ const net = require('net')
6
+ const { getName: getSocketName } = require('../autohttp/requestName')
7
+
8
+ // All valid options defined at https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids
9
+ const supportedProtocols = ['h2', 'http/1.1', 'http/1.0', 'http/0.9']
10
+
11
+ // Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/_http_agent.js#L350
12
+ function calculateServerName (options) {
13
+ let servername = options.host || ''
14
+ const hostHeader = options.headers && options.headers.host
15
+
16
+ if (hostHeader) {
17
+ if (typeof hostHeader !== 'string') {
18
+ throw new TypeError(
19
+ 'host header content must be a string, received' + hostHeader
20
+ )
21
+ }
22
+
23
+ // abc => abc
24
+ // abc:123 => abc
25
+ // [::1] => ::1
26
+ // [::1]:123 => ::1
27
+ if (hostHeader.startsWith('[')) {
28
+ const index = hostHeader.indexOf(']')
29
+ if (index === -1) {
30
+ // Leading '[', but no ']'. Need to do something...
31
+ servername = hostHeader
32
+ } else {
33
+ servername = hostHeader.substring(1, index)
34
+ }
35
+ } else {
36
+ servername = hostHeader.split(':', 1)[0]
37
+ }
38
+ }
39
+ // Don't implicitly set invalid (IP) servernames.
40
+ if (net.isIP(servername)) servername = ''
41
+ return servername
42
+ }
43
+
44
+ class AutoHttp2Agent extends EventEmitter {
45
+ constructor (options) {
46
+ super()
47
+ this.http2Agent = new Http2Agent(options)
48
+ this.httpsAgent = new https.Agent(options)
49
+ this.ALPNCache = new Map()
50
+ this.options = options
51
+ this.defaultPort = 443
52
+ }
53
+
54
+ createConnection (
55
+ req,
56
+ reqOptions,
57
+ cb,
58
+ socketCb
59
+ ) {
60
+ const options = {
61
+ ...reqOptions,
62
+ ...this.options,
63
+ port: Number(reqOptions.port || this.options.port || this.defaultPort),
64
+ host: reqOptions.hostname || reqOptions.host || 'localhost'
65
+ }
66
+
67
+ // check if ALPN is cached
68
+ const name = getSocketName(options)
69
+ const [protocol, cachedSocket] = this.ALPNCache.get(name) || []
70
+
71
+ if (!protocol || !cachedSocket || cachedSocket.closed || cachedSocket.destroyed) {
72
+ // No cache exists or the initial socket used to establish the connection has been closed. Perform ALPN again.
73
+ this.ALPNCache.delete(name)
74
+ this.createNewSocketConnection(req, options, cb, socketCb)
75
+ return
76
+ }
77
+
78
+ // No need to pass the cachedSocket since the respective protocol's agents will reuse the socket that was initially
79
+ // passed during ALPN Negotiation
80
+ if (protocol === 'h2') {
81
+ const http2Options = {
82
+ ...options,
83
+ path: options.socketPath
84
+ }
85
+
86
+ let connection
87
+ try {
88
+ const uri = options.uri
89
+ connection = this.http2Agent.createConnection(req, uri, http2Options)
90
+ } catch (e) {
91
+ cb(e)
92
+ connection && connection.socket && socketCb(connection.socket)
93
+ return
94
+ }
95
+
96
+ cb(null, 'http2', connection)
97
+ socketCb(connection.socket)
98
+
99
+ return
100
+ }
101
+
102
+ const http1RequestOptions = {
103
+ ...options,
104
+ agent: this.httpsAgent
105
+ }
106
+
107
+ let request
108
+ try {
109
+ request = https.request(http1RequestOptions)
110
+ } catch (e) {
111
+ cb(e)
112
+ return
113
+ }
114
+
115
+ request.on('socket', (socket) => socketCb(socket))
116
+ cb(null, 'http1', request)
117
+ }
118
+
119
+ createNewSocketConnection (req, options, cb, socketCb) {
120
+ const uri = options.uri
121
+ const name = getSocketName(options)
122
+
123
+ const socket = tls.connect({
124
+ ...options,
125
+ path: options.socketPath,
126
+ ALPNProtocols: supportedProtocols,
127
+ servername: options.servername || calculateServerName(options)
128
+ })
129
+ socketCb(socket)
130
+
131
+ const socketConnectionErrorHandler = (e) => {
132
+ cb(e)
133
+ }
134
+ socket.on('error', socketConnectionErrorHandler)
135
+
136
+ socket.once('secureConnect', () => {
137
+ socket.removeListener('error', socketConnectionErrorHandler)
138
+
139
+ const protocol = socket.alpnProtocol || 'http/1.1'
140
+
141
+ if (!supportedProtocols.includes(protocol)) {
142
+ cb(new Error('Unknown protocol' + protocol))
143
+ return
144
+ }
145
+
146
+ // Update the cache
147
+ this.ALPNCache.set(name, [protocol, socket])
148
+
149
+ socket.once('close', () => {
150
+ // Clean the cache when the socket closes
151
+ this.ALPNCache.delete(name)
152
+ })
153
+
154
+ if (protocol === 'h2') {
155
+ const http2Options = {
156
+ ...options,
157
+ path: options.socketPath
158
+ }
159
+ try {
160
+ const connection = this.http2Agent.createConnection(
161
+ req,
162
+ uri,
163
+ http2Options,
164
+ socket
165
+ )
166
+ cb(null, 'http2', connection)
167
+ } catch (e) {
168
+ cb(e)
169
+ }
170
+ return
171
+ }
172
+
173
+ // Protocol is http1, using the built in agent
174
+ // We need to release all free sockets so that new connection is created using the overridden createconnection
175
+ // forcing the agent to reuse the socket used for alpn
176
+
177
+ // This reassignment works, since all code so far is sync, and happens in the same tick, hence there will be no
178
+ // race conditions
179
+ const oldCreateConnection = this.httpsAgent.createConnection
180
+
181
+ this.httpsAgent.createConnection = () => {
182
+ return socket
183
+ }
184
+
185
+ const http1RequestOptions = {
186
+ ...options,
187
+ agent: this.httpsAgent
188
+ }
189
+ let request
190
+ try {
191
+ request = https.request(http1RequestOptions)
192
+ } catch (e) {
193
+ cb(e)
194
+ return
195
+ } finally {
196
+ this.httpsAgent.createConnection = oldCreateConnection
197
+ }
198
+ cb(null, 'http1', request)
199
+ })
200
+ }
201
+ }
202
+
203
+ module.exports = {
204
+ AutoHttp2Agent,
205
+ globalAgent: new AutoHttp2Agent({})
206
+ }
@@ -0,0 +1,40 @@
1
+ const {constants = {}} = require('http2')
2
+
3
+ // Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/util.js#L107
4
+ const kValidPseudoHeaders = new Set([
5
+ constants.HTTP2_HEADER_STATUS,
6
+ constants.HTTP2_HEADER_METHOD,
7
+ constants.HTTP2_HEADER_AUTHORITY,
8
+ constants.HTTP2_HEADER_SCHEME,
9
+ constants.HTTP2_HEADER_PATH
10
+ ])
11
+
12
+ // Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/util.js#L573
13
+ function assertValidPseudoHeader (header) {
14
+ if (!kValidPseudoHeaders.has(header)) {
15
+ throw new Error('Invalid PseudoHeader ' + header)
16
+ }
17
+ }
18
+
19
+ // Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/_http_common.js#L206
20
+ const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
21
+ function checkIsHttpToken (token) {
22
+ return RegExp(tokenRegExp).exec(token) !== null
23
+ }
24
+
25
+ // Referenced from https://github.com/nodejs/node/blob/0bf200b49a9a6eacdea6d5e5939cc2466506d532/lib/internal/http2/core.js#L1763
26
+ function validateRequestHeaders (headers) {
27
+ if (headers !== null && headers !== undefined) {
28
+ const keys = Object.keys(headers)
29
+ for (let i = 0; i < keys.length; i++) {
30
+ const header = keys[i]
31
+ if (header[0] === ':') {
32
+ assertValidPseudoHeader(header)
33
+ } else if (header && !checkIsHttpToken(header)) { throw new Error('Invalid HTTP Token: Header name' + header) }
34
+ }
35
+ }
36
+ }
37
+
38
+ module.exports = {
39
+ validateRequestHeaders
40
+ }
@@ -0,0 +1,8 @@
1
+ const { AutoHttp2Agent, globalAgent } = require('./agent')
2
+ const { request } = require('./request')
3
+
4
+ module.exports = {
5
+ Agent: AutoHttp2Agent,
6
+ request,
7
+ globalAgent
8
+ }