got 8.2.0 → 9.0.0

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,230 @@
1
+ 'use strict';
2
+ /* istanbul ignore next: compatibility reason */
3
+ const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; // TODO: Use the `URL` global when targeting Node.js 10
4
+ /* istanbul ignore next: compatibility reason */
5
+ const URLSearchParamsGlobal = typeof URLSearchParams === 'undefined' ? require('url').URLSearchParams : URLSearchParams;
6
+ const is = require('@sindresorhus/is');
7
+ const toReadableStream = require('to-readable-stream');
8
+ const urlParseLax = require('url-parse-lax');
9
+ const isRetryOnNetworkErrorAllowed = require('./is-retry-on-network-error-allowed');
10
+ const urlToOptions = require('./url-to-options');
11
+ const isFormData = require('./is-form-data');
12
+
13
+ const retryAfterStatusCodes = new Set([413, 429, 503]);
14
+ const knownHookEvents = ['beforeRequest'];
15
+
16
+ module.exports = (url, options, defaults) => {
17
+ if (Reflect.has(options, 'url') || (is.object(url) && Reflect.has(url, 'url'))) {
18
+ throw new TypeError('Parameter `url` is not an option. Use got(url, options)');
19
+ }
20
+
21
+ if (!is.string(url) && !is.object(url)) {
22
+ throw new TypeError(`Parameter \`url\` must be a string or object, not ${is(url)}`);
23
+ }
24
+
25
+ if (is.string(url)) {
26
+ if (options.baseUrl) {
27
+ url = urlToOptions(new URLGlobal(url, options.baseUrl));
28
+ } else {
29
+ url = url.replace(/^unix:/, 'http://$&');
30
+
31
+ try {
32
+ decodeURI(url);
33
+ } catch (_) {
34
+ throw new Error('Parameter `url` must contain valid UTF-8 character sequences');
35
+ }
36
+
37
+ url = urlParseLax(url);
38
+ if (url.auth) {
39
+ throw new Error('Basic authentication must be done with the `auth` option');
40
+ }
41
+ }
42
+ } else if (is(url) === 'URL') {
43
+ url = urlToOptions(url);
44
+ }
45
+
46
+ options = {
47
+ path: '',
48
+ headers: {},
49
+ ...url,
50
+ protocol: url.protocol || 'http:', // Override both null/undefined with default protocol
51
+ ...options
52
+ };
53
+
54
+ if (options.stream && options.json) {
55
+ options.json = false;
56
+ }
57
+
58
+ if (options.decompress && is.undefined(options.headers['accept-encoding'])) {
59
+ options.headers['accept-encoding'] = 'gzip, deflate';
60
+ }
61
+
62
+ const {query} = options;
63
+ if (query) {
64
+ if (!is.string(query)) {
65
+ options.query = (new URLSearchParamsGlobal(query)).toString();
66
+ }
67
+
68
+ options.path = `${options.path.split('?')[0]}?${options.query}`;
69
+ delete options.query;
70
+ }
71
+
72
+ if (options.json && is.undefined(options.headers.accept)) {
73
+ options.headers.accept = 'application/json';
74
+ }
75
+
76
+ const {headers} = options;
77
+ for (const [key, value] of Object.entries(headers)) {
78
+ if (is.nullOrUndefined(value)) {
79
+ delete headers[key];
80
+ }
81
+ }
82
+
83
+ const {body} = options;
84
+ if (is.nullOrUndefined(body)) {
85
+ options.method = (options.method || 'GET').toUpperCase();
86
+ } else {
87
+ const isObject = is.object(body) && !Buffer.isBuffer(body) && !is.nodeStream(body);
88
+ if (!is.nodeStream(body) && !is.string(body) && !is.buffer(body) && !(options.form || options.json)) {
89
+ throw new TypeError('The `body` option must be a stream.Readable, string or Buffer');
90
+ }
91
+
92
+ if (options.json && !(isObject || is.array(body))) {
93
+ throw new TypeError('The `body` option must be an Object or Array when the `json` option is used');
94
+ }
95
+
96
+ if (options.form && !isObject) {
97
+ throw new TypeError('The `body` option must be an Object when the `form` option is used');
98
+ }
99
+
100
+ if (isFormData(body)) {
101
+ // Special case for https://github.com/form-data/form-data
102
+ headers['content-type'] = headers['content-type'] || `multipart/form-data; boundary=${body.getBoundary()}`;
103
+ } else if (options.form) {
104
+ headers['content-type'] = headers['content-type'] || 'application/x-www-form-urlencoded';
105
+ options.body = (new URLSearchParamsGlobal(body)).toString();
106
+ } else if (options.json) {
107
+ headers['content-type'] = headers['content-type'] || 'application/json';
108
+ options.body = JSON.stringify(body);
109
+ }
110
+
111
+ // Convert buffer to stream to receive upload progress events (#322)
112
+ if (is.buffer(body)) {
113
+ options.body = toReadableStream(body);
114
+ options.body._buffer = body;
115
+ }
116
+
117
+ options.method = (options.method || 'POST').toUpperCase();
118
+ }
119
+
120
+ if (options.hostname === 'unix') {
121
+ const matches = /(.+?):(.+)/.exec(options.path);
122
+
123
+ if (matches) {
124
+ const [, socketPath, path] = matches;
125
+ options = {
126
+ ...options,
127
+ socketPath,
128
+ path,
129
+ host: null
130
+ };
131
+ }
132
+ }
133
+
134
+ options.gotRetry = {retries: 0, methods: [], statusCodes: []};
135
+ if (options.retry !== false) {
136
+ if (is.number(options.retry)) {
137
+ if (is.object(defaults.options.retry)) {
138
+ options.gotRetry = {...defaults.options.retry, retries: options.retry};
139
+ } else {
140
+ options.gotRetry.retries = options.retry;
141
+ }
142
+ } else {
143
+ options.gotRetry = {...options.gotRetry, ...options.retry};
144
+ }
145
+ delete options.retry;
146
+ }
147
+
148
+ options.gotRetry.methods = new Set(options.gotRetry.methods.map(method => method.toUpperCase()));
149
+ options.gotRetry.statusCodes = new Set(options.gotRetry.statusCodes);
150
+
151
+ if (!options.gotRetry.maxRetryAfter && Reflect.has(options, 'timeout')) {
152
+ if (is.number(options.timeout)) {
153
+ options.gotRetry.maxRetryAfter = options.timeout;
154
+ } else {
155
+ options.gotRetry.maxRetryAfter = Math.min(...[options.timeout.request, options.timeout.connection].filter(n => !is.nullOrUndefined(n)));
156
+ }
157
+ }
158
+
159
+ if (!is.function(options.gotRetry.retries)) {
160
+ const {retries} = options.gotRetry;
161
+
162
+ options.gotRetry.retries = (iteration, error) => {
163
+ if (iteration > retries || (!isRetryOnNetworkErrorAllowed(error) && (!options.gotRetry.methods.has(error.method) || !options.gotRetry.statusCodes.has(error.statusCode)))) {
164
+ return 0;
165
+ }
166
+
167
+ if (Reflect.has(error, 'headers') && Reflect.has(error.headers, 'retry-after') && retryAfterStatusCodes.has(error.statusCode)) {
168
+ let after = Number(error.headers['retry-after']);
169
+ if (is.nan(after)) {
170
+ after = Date.parse(error.headers['retry-after']) - Date.now();
171
+ } else {
172
+ after *= 1000;
173
+ }
174
+
175
+ if (after > options.gotRetry.maxRetryAfter) {
176
+ return 0;
177
+ }
178
+
179
+ return after;
180
+ }
181
+
182
+ if (error.statusCode === 413) {
183
+ return 0;
184
+ }
185
+
186
+ const noise = Math.random() * 100;
187
+
188
+ return ((1 << iteration) * 1000) + noise;
189
+ };
190
+ }
191
+
192
+ if (is.undefined(options.followRedirect)) {
193
+ options.followRedirect = true;
194
+ }
195
+
196
+ if (is.number(options.timeout) || is.object(options.timeout)) {
197
+ if (is.number(options.timeout)) {
198
+ options.gotTimeout = {request: options.timeout};
199
+ } else {
200
+ options.gotTimeout = options.timeout;
201
+ }
202
+ delete options.timeout;
203
+ }
204
+
205
+ if (is.nullOrUndefined(options.hooks)) {
206
+ options.hooks = {};
207
+ }
208
+ if (is.object(options.hooks)) {
209
+ for (const hookEvent of knownHookEvents) {
210
+ const hooks = options.hooks[hookEvent];
211
+ if (is.nullOrUndefined(hooks)) {
212
+ options.hooks[hookEvent] = [];
213
+ } else if (is.array(hooks)) {
214
+ for (const [index, hook] of hooks.entries()) {
215
+ if (!is.function(hook)) {
216
+ throw new TypeError(
217
+ `Parameter \`hooks.${hookEvent}[${index}]\` must be a function, not ${is(hook)}`
218
+ );
219
+ }
220
+ }
221
+ } else {
222
+ throw new TypeError(`Parameter \`hooks.${hookEvent}\` must be an array, not ${is(hooks)}`);
223
+ }
224
+ }
225
+ } else {
226
+ throw new TypeError(`Parameter \`hooks\` must be an object, not ${is(options.hooks)}`);
227
+ }
228
+
229
+ return options;
230
+ };
@@ -0,0 +1,71 @@
1
+ 'use strict';
2
+ module.exports = {
3
+ upload(request, emitter, uploadBodySize) {
4
+ const uploadEventFrequency = 150;
5
+ let uploaded = 0;
6
+ let progressInterval;
7
+
8
+ emitter.emit('uploadProgress', {
9
+ percent: 0,
10
+ transferred: 0,
11
+ total: uploadBodySize
12
+ });
13
+
14
+ request.once('error', () => {
15
+ clearInterval(progressInterval);
16
+ });
17
+
18
+ request.once('response', () => {
19
+ clearInterval(progressInterval);
20
+
21
+ emitter.emit('uploadProgress', {
22
+ percent: 1,
23
+ transferred: uploaded,
24
+ total: uploadBodySize
25
+ });
26
+ });
27
+
28
+ request.once('socket', socket => {
29
+ const onSocketConnect = () => {
30
+ progressInterval = setInterval(() => {
31
+ /* istanbul ignore next: hard to test */
32
+ if (socket.destroyed) {
33
+ clearInterval(progressInterval);
34
+ return;
35
+ }
36
+
37
+ const lastUploaded = uploaded;
38
+ /* istanbul ignore next: see #490 (occurs randomly!) */
39
+ const headersSize = request._header ? Buffer.byteLength(request._header) : 0;
40
+ uploaded = socket.bytesWritten - headersSize;
41
+
42
+ /* istanbul ignore next: see https://github.com/sindresorhus/got/pull/322#pullrequestreview-51647813 (no proof) */
43
+ if (uploadBodySize && uploaded > uploadBodySize) {
44
+ uploaded = uploadBodySize;
45
+ }
46
+
47
+ // Don't emit events with unchanged progress and
48
+ // prevent last event from being emitted, because
49
+ // it's emitted when `response` is emitted
50
+ if (uploaded === lastUploaded || uploaded === uploadBodySize) {
51
+ return;
52
+ }
53
+
54
+ emitter.emit('uploadProgress', {
55
+ percent: uploadBodySize ? uploaded / uploadBodySize : 0,
56
+ transferred: uploaded,
57
+ total: uploadBodySize
58
+ });
59
+ }, uploadEventFrequency);
60
+ };
61
+
62
+ if (socket.connecting) {
63
+ socket.once('connect', onSocketConnect);
64
+ } else {
65
+ // The socket is being reused from pool,
66
+ // so the connect event will not be emitted
67
+ onSocketConnect();
68
+ }
69
+ });
70
+ }
71
+ };
@@ -0,0 +1,185 @@
1
+ 'use strict';
2
+ /* istanbul ignore next: compatibility reason */
3
+ const URLGlobal = typeof URL === 'undefined' ? require('url').URL : URL; // TODO: Use the `URL` global when targeting Node.js 10
4
+ const EventEmitter = require('events');
5
+ const http = require('http');
6
+ const https = require('https');
7
+ const urlLib = require('url');
8
+ const CacheableRequest = require('cacheable-request');
9
+ const is = require('@sindresorhus/is');
10
+ const timedOut = require('./timed-out');
11
+ const getBodySize = require('./get-body-size');
12
+ const getResponse = require('./get-response');
13
+ const progress = require('./progress');
14
+ const {GotError, CacheError, UnsupportedProtocolError, MaxRedirectsError, RequestError} = require('./errors');
15
+
16
+ const getMethodRedirectCodes = new Set([300, 301, 302, 303, 304, 305, 307, 308]);
17
+ const allMethodRedirectCodes = new Set([300, 303, 307, 308]);
18
+
19
+ module.exports = options => {
20
+ const emitter = new EventEmitter();
21
+ const requestUrl = options.href || (new URLGlobal(options.path, urlLib.format(options))).toString();
22
+ const redirects = [];
23
+ const agents = is.object(options.agent) ? options.agent : null;
24
+ let retryCount = 0;
25
+ let retryTries = 0;
26
+ let redirectUrl;
27
+ let uploadBodySize;
28
+
29
+ const get = options => {
30
+ if (options.protocol !== 'http:' && options.protocol !== 'https:') {
31
+ emitter.emit('error', new UnsupportedProtocolError(options));
32
+ return;
33
+ }
34
+
35
+ let fn = options.protocol === 'https:' ? https : http;
36
+
37
+ if (agents) {
38
+ const protocolName = options.protocol === 'https:' ? 'https' : 'http';
39
+ options.agent = agents[protocolName] || options.agent;
40
+ }
41
+
42
+ /* istanbul ignore next: electron.net is broken */
43
+ if (options.useElectronNet && process.versions.electron) {
44
+ const electron = global['require']('electron'); // eslint-disable-line dot-notation
45
+ fn = electron.net || electron.remote.net;
46
+ }
47
+
48
+ const cacheableRequest = new CacheableRequest(fn.request, options.cache);
49
+ const cacheReq = cacheableRequest(options, response => {
50
+ const {statusCode} = response;
51
+ response.retryCount = retryCount;
52
+ response.url = redirectUrl || requestUrl;
53
+ response.requestUrl = requestUrl;
54
+
55
+ const followRedirect = options.followRedirect && 'location' in response.headers;
56
+ const redirectGet = followRedirect && getMethodRedirectCodes.has(statusCode);
57
+ const redirectAll = followRedirect && allMethodRedirectCodes.has(statusCode);
58
+
59
+ if (redirectAll || (redirectGet && (options.method === 'GET' || options.method === 'HEAD'))) {
60
+ response.resume();
61
+
62
+ if (statusCode === 303) {
63
+ // Server responded with "see other", indicating that the resource exists at another location,
64
+ // and the client should request it from that location via GET or HEAD.
65
+ options.method = 'GET';
66
+ }
67
+
68
+ if (redirects.length >= 10) {
69
+ emitter.emit('error', new MaxRedirectsError(statusCode, redirects, options), null, response);
70
+ return;
71
+ }
72
+
73
+ const bufferString = Buffer.from(response.headers.location, 'binary').toString();
74
+ redirectUrl = (new URLGlobal(bufferString, urlLib.format(options))).toString();
75
+
76
+ try {
77
+ redirectUrl = decodeURI(redirectUrl);
78
+ } catch (error) {
79
+ emitter.emit('error', error);
80
+ return;
81
+ }
82
+
83
+ redirects.push(redirectUrl);
84
+
85
+ const redirectOpts = {
86
+ ...options,
87
+ ...urlLib.parse(redirectUrl)
88
+ };
89
+
90
+ emitter.emit('redirect', response, redirectOpts);
91
+
92
+ get(redirectOpts);
93
+ return;
94
+ }
95
+
96
+ try {
97
+ getResponse(response, options, emitter, redirects);
98
+ } catch (error) {
99
+ emitter.emit('error', error);
100
+ }
101
+ });
102
+
103
+ cacheReq.on('error', error => {
104
+ if (error instanceof CacheableRequest.RequestError) {
105
+ emitter.emit('error', new RequestError(error, options));
106
+ } else {
107
+ emitter.emit('error', new CacheError(error, options));
108
+ }
109
+ });
110
+
111
+ cacheReq.once('request', request => {
112
+ let aborted = false;
113
+ request.once('abort', _ => {
114
+ aborted = true;
115
+ });
116
+
117
+ request.once('error', error => {
118
+ if (aborted) {
119
+ return;
120
+ }
121
+
122
+ if (!(error instanceof GotError)) {
123
+ error = new RequestError(error, options);
124
+ }
125
+ emitter.emit('retry', error, retried => {
126
+ if (!retried) {
127
+ emitter.emit('error', error);
128
+ }
129
+ });
130
+ });
131
+
132
+ progress.upload(request, emitter, uploadBodySize);
133
+
134
+ if (options.gotTimeout) {
135
+ timedOut(request, options);
136
+ }
137
+
138
+ emitter.emit('request', request);
139
+ });
140
+ };
141
+
142
+ emitter.on('retry', (error, cb) => {
143
+ let backoff;
144
+ try {
145
+ backoff = options.gotRetry.retries(++retryTries, error);
146
+ } catch (error) {
147
+ emitter.emit('error', error);
148
+ return;
149
+ }
150
+
151
+ if (backoff) {
152
+ retryCount++;
153
+ setTimeout(get, backoff, options);
154
+ cb(true);
155
+ return;
156
+ }
157
+
158
+ cb(false);
159
+ });
160
+
161
+ setImmediate(async () => {
162
+ try {
163
+ uploadBodySize = await getBodySize(options);
164
+
165
+ if (
166
+ uploadBodySize > 0 &&
167
+ is.undefined(options.headers['content-length']) &&
168
+ is.undefined(options.headers['transfer-encoding'])
169
+ ) {
170
+ options.headers['content-length'] = uploadBodySize;
171
+ }
172
+
173
+ for (const hook of options.hooks.beforeRequest) {
174
+ // eslint-disable-next-line no-await-in-loop
175
+ await hook(options);
176
+ }
177
+
178
+ get(options);
179
+ } catch (error) {
180
+ emitter.emit('error', error);
181
+ }
182
+ });
183
+
184
+ return emitter;
185
+ };
@@ -0,0 +1,160 @@
1
+ 'use strict';
2
+ const net = require('net');
3
+ const {TimeoutError} = require('./errors');
4
+
5
+ const reentry = Symbol('reentry');
6
+
7
+ function addTimeout(delay, callback, ...args) {
8
+ // Event loop order is timers, poll, immediates.
9
+ // The timed event may emit during the current tick poll phase, so
10
+ // defer calling the handler until the poll phase completes.
11
+ let immediate;
12
+ const timeout = setTimeout(
13
+ () => {
14
+ immediate = setImmediate(callback, delay, ...args);
15
+ /* istanbul ignore next: added in node v9.7.0 */
16
+ if (immediate.unref) {
17
+ immediate.unref();
18
+ }
19
+ },
20
+ delay
21
+ );
22
+ timeout.unref();
23
+ return () => {
24
+ clearTimeout(timeout);
25
+ clearImmediate(immediate);
26
+ };
27
+ }
28
+
29
+ module.exports = (request, options) => {
30
+ /* istanbul ignore next: this makes sure timed-out isn't called twice */
31
+ if (request[reentry]) {
32
+ return;
33
+ }
34
+
35
+ request[reentry] = true;
36
+ const {gotTimeout: delays, host, hostname} = options;
37
+ const timeoutHandler = (delay, event) => {
38
+ request.abort();
39
+ request.emit('error', new TimeoutError(delay, event, options));
40
+ };
41
+
42
+ const cancelers = [];
43
+ const cancelTimeouts = () => {
44
+ cancelers.forEach(cancelTimeout => cancelTimeout());
45
+ };
46
+
47
+ request.on('error', cancelTimeouts);
48
+ request.once('response', response => {
49
+ response.once('end', cancelTimeouts);
50
+ });
51
+
52
+ if (delays.request !== undefined) {
53
+ const cancelTimeout = addTimeout(
54
+ delays.request,
55
+ timeoutHandler,
56
+ 'request'
57
+ );
58
+ cancelers.push(cancelTimeout);
59
+ }
60
+
61
+ if (delays.socket !== undefined) {
62
+ request.setTimeout(
63
+ delays.socket,
64
+ () => {
65
+ timeoutHandler(delays.socket, 'socket');
66
+ }
67
+ );
68
+ }
69
+
70
+ if (delays.lookup !== undefined && !request.socketPath && !net.isIP(hostname || host)) {
71
+ request.once('socket', socket => {
72
+ /* istanbul ignore next: hard to test */
73
+ if (socket.connecting) {
74
+ const cancelTimeout = addTimeout(
75
+ delays.lookup,
76
+ timeoutHandler,
77
+ 'lookup'
78
+ );
79
+ cancelers.push(cancelTimeout);
80
+ socket.once('lookup', cancelTimeout);
81
+ }
82
+ });
83
+ }
84
+
85
+ if (delays.connect !== undefined) {
86
+ request.once('socket', socket => {
87
+ /* istanbul ignore next: hard to test */
88
+ if (socket.connecting) {
89
+ const timeConnect = () => {
90
+ const cancelTimeout = addTimeout(
91
+ delays.connect,
92
+ timeoutHandler,
93
+ 'connect'
94
+ );
95
+ cancelers.push(cancelTimeout);
96
+ return cancelTimeout;
97
+ };
98
+
99
+ if (request.socketPath || net.isIP(hostname || host)) {
100
+ socket.once('connect', timeConnect());
101
+ } else {
102
+ socket.once('lookup', () => {
103
+ socket.once('connect', timeConnect());
104
+ });
105
+ }
106
+ }
107
+ });
108
+ }
109
+
110
+ if (delays.secureConnect !== undefined && options.protocol === 'https:') {
111
+ request.once('socket', socket => {
112
+ /* istanbul ignore next: hard to test */
113
+ if (socket.connecting) {
114
+ socket.once('connect', () => {
115
+ const cancelTimeout = addTimeout(
116
+ delays.secureConnect,
117
+ timeoutHandler,
118
+ 'secureConnect'
119
+ );
120
+ cancelers.push(cancelTimeout);
121
+ socket.once('secureConnect', cancelTimeout);
122
+ });
123
+ }
124
+ });
125
+ }
126
+
127
+ if (delays.send !== undefined) {
128
+ request.once('socket', socket => {
129
+ const timeRequest = () => {
130
+ const cancelTimeout = addTimeout(
131
+ delays.send,
132
+ timeoutHandler,
133
+ 'send'
134
+ );
135
+ cancelers.push(cancelTimeout);
136
+ return cancelTimeout;
137
+ };
138
+ /* istanbul ignore next: hard to test */
139
+ if (socket.connecting) {
140
+ socket.once('connect', () => {
141
+ request.once('upload-complete', timeRequest());
142
+ });
143
+ } else {
144
+ request.once('upload-complete', timeRequest());
145
+ }
146
+ });
147
+ }
148
+
149
+ if (delays.response !== undefined) {
150
+ request.once('upload-complete', () => {
151
+ const cancelTimeout = addTimeout(
152
+ delays.response,
153
+ timeoutHandler,
154
+ 'response'
155
+ );
156
+ cancelers.push(cancelTimeout);
157
+ request.once('response', cancelTimeout);
158
+ });
159
+ }
160
+ };
@@ -0,0 +1,25 @@
1
+ 'use strict';
2
+ const is = require('@sindresorhus/is');
3
+
4
+ module.exports = url => {
5
+ const options = {
6
+ protocol: url.protocol,
7
+ hostname: url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname,
8
+ hash: url.hash,
9
+ search: url.search,
10
+ pathname: url.pathname,
11
+ href: url.href
12
+ };
13
+
14
+ if (is.string(url.port) && url.port.length > 0) {
15
+ options.port = Number(url.port);
16
+ }
17
+
18
+ if (url.username || url.password) {
19
+ options.auth = `${url.username}:${url.password}`;
20
+ }
21
+
22
+ options.path = is.null(url.search) ? url.pathname : `${url.pathname}${url.search}`;
23
+
24
+ return options;
25
+ };