source: trip-planner-front/node_modules/follow-redirects/index.js@ 8d391a1

Last change on this file since 8d391a1 was e29cc2e, checked in by Ema <ema_spirova@…>, 3 years ago

primeNG components

  • Property mode set to 100644
File size: 17.1 KB
Line 
1var url = require("url");
2var URL = url.URL;
3var http = require("http");
4var https = require("https");
5var Writable = require("stream").Writable;
6var assert = require("assert");
7var debug = require("./debug");
8
9// Create handlers that pass events from native requests
10var events = ["abort", "aborted", "connect", "error", "socket", "timeout"];
11var eventHandlers = Object.create(null);
12events.forEach(function (event) {
13 eventHandlers[event] = function (arg1, arg2, arg3) {
14 this._redirectable.emit(event, arg1, arg2, arg3);
15 };
16});
17
18// Error types with codes
19var RedirectionError = createErrorType(
20 "ERR_FR_REDIRECTION_FAILURE",
21 "Redirected request failed"
22);
23var TooManyRedirectsError = createErrorType(
24 "ERR_FR_TOO_MANY_REDIRECTS",
25 "Maximum number of redirects exceeded"
26);
27var MaxBodyLengthExceededError = createErrorType(
28 "ERR_FR_MAX_BODY_LENGTH_EXCEEDED",
29 "Request body larger than maxBodyLength limit"
30);
31var WriteAfterEndError = createErrorType(
32 "ERR_STREAM_WRITE_AFTER_END",
33 "write after end"
34);
35
36// An HTTP(S) request that can be redirected
37function RedirectableRequest(options, responseCallback) {
38 // Initialize the request
39 Writable.call(this);
40 this._sanitizeOptions(options);
41 this._options = options;
42 this._ended = false;
43 this._ending = false;
44 this._redirectCount = 0;
45 this._redirects = [];
46 this._requestBodyLength = 0;
47 this._requestBodyBuffers = [];
48
49 // Attach a callback if passed
50 if (responseCallback) {
51 this.on("response", responseCallback);
52 }
53
54 // React to responses of native requests
55 var self = this;
56 this._onNativeResponse = function (response) {
57 self._processResponse(response);
58 };
59
60 // Perform the first request
61 this._performRequest();
62}
63RedirectableRequest.prototype = Object.create(Writable.prototype);
64
65RedirectableRequest.prototype.abort = function () {
66 abortRequest(this._currentRequest);
67 this.emit("abort");
68};
69
70// Writes buffered data to the current native request
71RedirectableRequest.prototype.write = function (data, encoding, callback) {
72 // Writing is not allowed if end has been called
73 if (this._ending) {
74 throw new WriteAfterEndError();
75 }
76
77 // Validate input and shift parameters if necessary
78 if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) {
79 throw new TypeError("data should be a string, Buffer or Uint8Array");
80 }
81 if (typeof encoding === "function") {
82 callback = encoding;
83 encoding = null;
84 }
85
86 // Ignore empty buffers, since writing them doesn't invoke the callback
87 // https://github.com/nodejs/node/issues/22066
88 if (data.length === 0) {
89 if (callback) {
90 callback();
91 }
92 return;
93 }
94 // Only write when we don't exceed the maximum body length
95 if (this._requestBodyLength + data.length <= this._options.maxBodyLength) {
96 this._requestBodyLength += data.length;
97 this._requestBodyBuffers.push({ data: data, encoding: encoding });
98 this._currentRequest.write(data, encoding, callback);
99 }
100 // Error when we exceed the maximum body length
101 else {
102 this.emit("error", new MaxBodyLengthExceededError());
103 this.abort();
104 }
105};
106
107// Ends the current native request
108RedirectableRequest.prototype.end = function (data, encoding, callback) {
109 // Shift parameters if necessary
110 if (typeof data === "function") {
111 callback = data;
112 data = encoding = null;
113 }
114 else if (typeof encoding === "function") {
115 callback = encoding;
116 encoding = null;
117 }
118
119 // Write data if needed and end
120 if (!data) {
121 this._ended = this._ending = true;
122 this._currentRequest.end(null, null, callback);
123 }
124 else {
125 var self = this;
126 var currentRequest = this._currentRequest;
127 this.write(data, encoding, function () {
128 self._ended = true;
129 currentRequest.end(null, null, callback);
130 });
131 this._ending = true;
132 }
133};
134
135// Sets a header value on the current native request
136RedirectableRequest.prototype.setHeader = function (name, value) {
137 this._options.headers[name] = value;
138 this._currentRequest.setHeader(name, value);
139};
140
141// Clears a header value on the current native request
142RedirectableRequest.prototype.removeHeader = function (name) {
143 delete this._options.headers[name];
144 this._currentRequest.removeHeader(name);
145};
146
147// Global timeout for all underlying requests
148RedirectableRequest.prototype.setTimeout = function (msecs, callback) {
149 var self = this;
150
151 // Destroys the socket on timeout
152 function destroyOnTimeout(socket) {
153 socket.setTimeout(msecs);
154 socket.removeListener("timeout", socket.destroy);
155 socket.addListener("timeout", socket.destroy);
156 }
157
158 // Sets up a timer to trigger a timeout event
159 function startTimer(socket) {
160 if (self._timeout) {
161 clearTimeout(self._timeout);
162 }
163 self._timeout = setTimeout(function () {
164 self.emit("timeout");
165 clearTimer();
166 }, msecs);
167 destroyOnTimeout(socket);
168 }
169
170 // Stops a timeout from triggering
171 function clearTimer() {
172 // Clear the timeout
173 if (self._timeout) {
174 clearTimeout(self._timeout);
175 self._timeout = null;
176 }
177
178 // Clean up all attached listeners
179 self.removeListener("abort", clearTimer);
180 self.removeListener("error", clearTimer);
181 self.removeListener("response", clearTimer);
182 if (callback) {
183 self.removeListener("timeout", callback);
184 }
185 if (!self.socket) {
186 self._currentRequest.removeListener("socket", startTimer);
187 }
188 }
189
190 // Attach callback if passed
191 if (callback) {
192 this.on("timeout", callback);
193 }
194
195 // Start the timer if or when the socket is opened
196 if (this.socket) {
197 startTimer(this.socket);
198 }
199 else {
200 this._currentRequest.once("socket", startTimer);
201 }
202
203 // Clean up on events
204 this.on("socket", destroyOnTimeout);
205 this.on("abort", clearTimer);
206 this.on("error", clearTimer);
207 this.on("response", clearTimer);
208
209 return this;
210};
211
212// Proxy all other public ClientRequest methods
213[
214 "flushHeaders", "getHeader",
215 "setNoDelay", "setSocketKeepAlive",
216].forEach(function (method) {
217 RedirectableRequest.prototype[method] = function (a, b) {
218 return this._currentRequest[method](a, b);
219 };
220});
221
222// Proxy all public ClientRequest properties
223["aborted", "connection", "socket"].forEach(function (property) {
224 Object.defineProperty(RedirectableRequest.prototype, property, {
225 get: function () { return this._currentRequest[property]; },
226 });
227});
228
229RedirectableRequest.prototype._sanitizeOptions = function (options) {
230 // Ensure headers are always present
231 if (!options.headers) {
232 options.headers = {};
233 }
234
235 // Since http.request treats host as an alias of hostname,
236 // but the url module interprets host as hostname plus port,
237 // eliminate the host property to avoid confusion.
238 if (options.host) {
239 // Use hostname if set, because it has precedence
240 if (!options.hostname) {
241 options.hostname = options.host;
242 }
243 delete options.host;
244 }
245
246 // Complete the URL object when necessary
247 if (!options.pathname && options.path) {
248 var searchPos = options.path.indexOf("?");
249 if (searchPos < 0) {
250 options.pathname = options.path;
251 }
252 else {
253 options.pathname = options.path.substring(0, searchPos);
254 options.search = options.path.substring(searchPos);
255 }
256 }
257};
258
259
260// Executes the next native request (initial or redirect)
261RedirectableRequest.prototype._performRequest = function () {
262 // Load the native protocol
263 var protocol = this._options.protocol;
264 var nativeProtocol = this._options.nativeProtocols[protocol];
265 if (!nativeProtocol) {
266 this.emit("error", new TypeError("Unsupported protocol " + protocol));
267 return;
268 }
269
270 // If specified, use the agent corresponding to the protocol
271 // (HTTP and HTTPS use different types of agents)
272 if (this._options.agents) {
273 var scheme = protocol.substr(0, protocol.length - 1);
274 this._options.agent = this._options.agents[scheme];
275 }
276
277 // Create the native request
278 var request = this._currentRequest =
279 nativeProtocol.request(this._options, this._onNativeResponse);
280 this._currentUrl = url.format(this._options);
281
282 // Set up event handlers
283 request._redirectable = this;
284 for (var e = 0; e < events.length; e++) {
285 request.on(events[e], eventHandlers[events[e]]);
286 }
287
288 // End a redirected request
289 // (The first request must be ended explicitly with RedirectableRequest#end)
290 if (this._isRedirect) {
291 // Write the request entity and end.
292 var i = 0;
293 var self = this;
294 var buffers = this._requestBodyBuffers;
295 (function writeNext(error) {
296 // Only write if this request has not been redirected yet
297 /* istanbul ignore else */
298 if (request === self._currentRequest) {
299 // Report any write errors
300 /* istanbul ignore if */
301 if (error) {
302 self.emit("error", error);
303 }
304 // Write the next buffer if there are still left
305 else if (i < buffers.length) {
306 var buffer = buffers[i++];
307 /* istanbul ignore else */
308 if (!request.finished) {
309 request.write(buffer.data, buffer.encoding, writeNext);
310 }
311 }
312 // End the request if `end` has been called on us
313 else if (self._ended) {
314 request.end();
315 }
316 }
317 }());
318 }
319};
320
321// Processes a response from the current native request
322RedirectableRequest.prototype._processResponse = function (response) {
323 // Store the redirected response
324 var statusCode = response.statusCode;
325 if (this._options.trackRedirects) {
326 this._redirects.push({
327 url: this._currentUrl,
328 headers: response.headers,
329 statusCode: statusCode,
330 });
331 }
332
333 // RFC7231§6.4: The 3xx (Redirection) class of status code indicates
334 // that further action needs to be taken by the user agent in order to
335 // fulfill the request. If a Location header field is provided,
336 // the user agent MAY automatically redirect its request to the URI
337 // referenced by the Location field value,
338 // even if the specific status code is not understood.
339 var location = response.headers.location;
340 if (location && this._options.followRedirects !== false &&
341 statusCode >= 300 && statusCode < 400) {
342 // Abort the current request
343 abortRequest(this._currentRequest);
344 // Discard the remainder of the response to avoid waiting for data
345 response.destroy();
346
347 // RFC7231§6.4: A client SHOULD detect and intervene
348 // in cyclical redirections (i.e., "infinite" redirection loops).
349 if (++this._redirectCount > this._options.maxRedirects) {
350 this.emit("error", new TooManyRedirectsError());
351 return;
352 }
353
354 // RFC7231§6.4: Automatic redirection needs to done with
355 // care for methods not known to be safe, […]
356 // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change
357 // the request method from POST to GET for the subsequent request.
358 if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" ||
359 // RFC7231§6.4.4: The 303 (See Other) status code indicates that
360 // the server is redirecting the user agent to a different resource […]
361 // A user agent can perform a retrieval request targeting that URI
362 // (a GET or HEAD request if using HTTP) […]
363 (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) {
364 this._options.method = "GET";
365 // Drop a possible entity and headers related to it
366 this._requestBodyBuffers = [];
367 removeMatchingHeaders(/^content-/i, this._options.headers);
368 }
369
370 // Drop the Host header, as the redirect might lead to a different host
371 var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers);
372
373 // If the redirect is relative, carry over the host of the last request
374 var currentUrlParts = url.parse(this._currentUrl);
375 var currentHost = currentHostHeader || currentUrlParts.host;
376 var currentUrl = /^\w+:/.test(location) ? this._currentUrl :
377 url.format(Object.assign(currentUrlParts, { host: currentHost }));
378
379 // Determine the URL of the redirection
380 var redirectUrl;
381 try {
382 redirectUrl = url.resolve(currentUrl, location);
383 }
384 catch (cause) {
385 this.emit("error", new RedirectionError(cause));
386 return;
387 }
388
389 // Create the redirected request
390 debug("redirecting to", redirectUrl);
391 this._isRedirect = true;
392 var redirectUrlParts = url.parse(redirectUrl);
393 Object.assign(this._options, redirectUrlParts);
394
395 // Drop the Authorization header if redirecting to another domain
396 if (!(redirectUrlParts.host === currentHost || isSubdomainOf(redirectUrlParts.host, currentHost))) {
397 removeMatchingHeaders(/^authorization$/i, this._options.headers);
398 }
399
400 // Evaluate the beforeRedirect callback
401 if (typeof this._options.beforeRedirect === "function") {
402 var responseDetails = { headers: response.headers };
403 try {
404 this._options.beforeRedirect.call(null, this._options, responseDetails);
405 }
406 catch (err) {
407 this.emit("error", err);
408 return;
409 }
410 this._sanitizeOptions(this._options);
411 }
412
413 // Perform the redirected request
414 try {
415 this._performRequest();
416 }
417 catch (cause) {
418 this.emit("error", new RedirectionError(cause));
419 }
420 }
421 else {
422 // The response is not a redirect; return it as-is
423 response.responseUrl = this._currentUrl;
424 response.redirects = this._redirects;
425 this.emit("response", response);
426
427 // Clean up
428 this._requestBodyBuffers = [];
429 }
430};
431
432// Wraps the key/value object of protocols with redirect functionality
433function wrap(protocols) {
434 // Default settings
435 var exports = {
436 maxRedirects: 21,
437 maxBodyLength: 10 * 1024 * 1024,
438 };
439
440 // Wrap each protocol
441 var nativeProtocols = {};
442 Object.keys(protocols).forEach(function (scheme) {
443 var protocol = scheme + ":";
444 var nativeProtocol = nativeProtocols[protocol] = protocols[scheme];
445 var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol);
446
447 // Executes a request, following redirects
448 function request(input, options, callback) {
449 // Parse parameters
450 if (typeof input === "string") {
451 var urlStr = input;
452 try {
453 input = urlToOptions(new URL(urlStr));
454 }
455 catch (err) {
456 /* istanbul ignore next */
457 input = url.parse(urlStr);
458 }
459 }
460 else if (URL && (input instanceof URL)) {
461 input = urlToOptions(input);
462 }
463 else {
464 callback = options;
465 options = input;
466 input = { protocol: protocol };
467 }
468 if (typeof options === "function") {
469 callback = options;
470 options = null;
471 }
472
473 // Set defaults
474 options = Object.assign({
475 maxRedirects: exports.maxRedirects,
476 maxBodyLength: exports.maxBodyLength,
477 }, input, options);
478 options.nativeProtocols = nativeProtocols;
479
480 assert.equal(options.protocol, protocol, "protocol mismatch");
481 debug("options", options);
482 return new RedirectableRequest(options, callback);
483 }
484
485 // Executes a GET request, following redirects
486 function get(input, options, callback) {
487 var wrappedRequest = wrappedProtocol.request(input, options, callback);
488 wrappedRequest.end();
489 return wrappedRequest;
490 }
491
492 // Expose the properties on the wrapped protocol
493 Object.defineProperties(wrappedProtocol, {
494 request: { value: request, configurable: true, enumerable: true, writable: true },
495 get: { value: get, configurable: true, enumerable: true, writable: true },
496 });
497 });
498 return exports;
499}
500
501/* istanbul ignore next */
502function noop() { /* empty */ }
503
504// from https://github.com/nodejs/node/blob/master/lib/internal/url.js
505function urlToOptions(urlObject) {
506 var options = {
507 protocol: urlObject.protocol,
508 hostname: urlObject.hostname.startsWith("[") ?
509 /* istanbul ignore next */
510 urlObject.hostname.slice(1, -1) :
511 urlObject.hostname,
512 hash: urlObject.hash,
513 search: urlObject.search,
514 pathname: urlObject.pathname,
515 path: urlObject.pathname + urlObject.search,
516 href: urlObject.href,
517 };
518 if (urlObject.port !== "") {
519 options.port = Number(urlObject.port);
520 }
521 return options;
522}
523
524function removeMatchingHeaders(regex, headers) {
525 var lastValue;
526 for (var header in headers) {
527 if (regex.test(header)) {
528 lastValue = headers[header].toString().trim();
529 delete headers[header];
530 }
531 }
532 return lastValue;
533}
534
535function createErrorType(code, defaultMessage) {
536 function CustomError(cause) {
537 Error.captureStackTrace(this, this.constructor);
538 if (!cause) {
539 this.message = defaultMessage;
540 }
541 else {
542 this.message = defaultMessage + ": " + cause.message;
543 this.cause = cause;
544 }
545 }
546 CustomError.prototype = new Error();
547 CustomError.prototype.constructor = CustomError;
548 CustomError.prototype.name = "Error [" + code + "]";
549 CustomError.prototype.code = code;
550 return CustomError;
551}
552
553function abortRequest(request) {
554 for (var e = 0; e < events.length; e++) {
555 request.removeListener(events[e], eventHandlers[events[e]]);
556 }
557 request.on("error", noop);
558 request.abort();
559}
560
561function isSubdomainOf(subdomain, domain) {
562 const dot = subdomain.length - domain.length - 1;
563 return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain);
564}
565
566// Exports
567module.exports = wrap({ http: http, https: https });
568module.exports.wrap = wrap;
Note: See TracBrowser for help on using the repository browser.