| var url = require("url"); | |
| var URL = url.URL; | |
| var http = require("http"); | |
| var https = require("https"); | |
| var Writable = require("stream").Writable; | |
| var assert = require("assert"); | |
| var debug = require("./debug"); | |
| // Preventive platform detection | |
| // istanbul ignore next | |
| (function detectUnsupportedEnvironment() { | |
| var looksLikeNode = typeof process !== "undefined"; | |
| var looksLikeBrowser = typeof window !== "undefined" && typeof document !== "undefined"; | |
| var looksLikeV8 = isFunction(Error.captureStackTrace); | |
| if (!looksLikeNode && (looksLikeBrowser || !looksLikeV8)) { | |
| console.warn("The follow-redirects package should be excluded from browser builds."); | |
| } | |
| }()); | |
| // Whether to use the native URL object or the legacy url module | |
| var useNativeURL = false; | |
| try { | |
| assert(new URL("")); | |
| } | |
| catch (error) { | |
| useNativeURL = error.code === "ERR_INVALID_URL"; | |
| } | |
| // URL fields to preserve in copy operations | |
| var preservedUrlFields = [ | |
| "auth", | |
| "host", | |
| "hostname", | |
| "href", | |
| "path", | |
| "pathname", | |
| "port", | |
| "protocol", | |
| "query", | |
| "search", | |
| "hash", | |
| ]; | |
| // Create handlers that pass events from native requests | |
| var events = ["abort", "aborted", "connect", "error", "socket", "timeout"]; | |
| var eventHandlers = Object.create(null); | |
| events.forEach(function (event) { | |
| eventHandlers[event] = function (arg1, arg2, arg3) { | |
| this._redirectable.emit(event, arg1, arg2, arg3); | |
| }; | |
| }); | |
| // Error types with codes | |
| var InvalidUrlError = createErrorType( | |
| "ERR_INVALID_URL", | |
| "Invalid URL", | |
| TypeError | |
| ); | |
| var RedirectionError = createErrorType( | |
| "ERR_FR_REDIRECTION_FAILURE", | |
| "Redirected request failed" | |
| ); | |
| var TooManyRedirectsError = createErrorType( | |
| "ERR_FR_TOO_MANY_REDIRECTS", | |
| "Maximum number of redirects exceeded", | |
| RedirectionError | |
| ); | |
| var MaxBodyLengthExceededError = createErrorType( | |
| "ERR_FR_MAX_BODY_LENGTH_EXCEEDED", | |
| "Request body larger than maxBodyLength limit" | |
| ); | |
| var WriteAfterEndError = createErrorType( | |
| "ERR_STREAM_WRITE_AFTER_END", | |
| "write after end" | |
| ); | |
| // istanbul ignore next | |
| var destroy = Writable.prototype.destroy || noop; | |
| // An HTTP(S) request that can be redirected | |
| function RedirectableRequest(options, responseCallback) { | |
| // Initialize the request | |
| Writable.call(this); | |
| this._sanitizeOptions(options); | |
| this._options = options; | |
| this._ended = false; | |
| this._ending = false; | |
| this._redirectCount = 0; | |
| this._redirects = []; | |
| this._requestBodyLength = 0; | |
| this._requestBodyBuffers = []; | |
| // Attach a callback if passed | |
| if (responseCallback) { | |
| this.on("response", responseCallback); | |
| } | |
| // React to responses of native requests | |
| var self = this; | |
| this._onNativeResponse = function (response) { | |
| try { | |
| self._processResponse(response); | |
| } | |
| catch (cause) { | |
| self.emit("error", cause instanceof RedirectionError ? | |
| cause : new RedirectionError({ cause: cause })); | |
| } | |
| }; | |
| // Perform the first request | |
| this._performRequest(); | |
| } | |
| RedirectableRequest.prototype = Object.create(Writable.prototype); | |
| RedirectableRequest.prototype.abort = function () { | |
| destroyRequest(this._currentRequest); | |
| this._currentRequest.abort(); | |
| this.emit("abort"); | |
| }; | |
| RedirectableRequest.prototype.destroy = function (error) { | |
| destroyRequest(this._currentRequest, error); | |
| destroy.call(this, error); | |
| return this; | |
| }; | |
| // Writes buffered data to the current native request | |
| RedirectableRequest.prototype.write = function (data, encoding, callback) { | |
| // Writing is not allowed if end has been called | |
| if (this._ending) { | |
| throw new WriteAfterEndError(); | |
| } | |
| // Validate input and shift parameters if necessary | |
| if (!isString(data) && !isBuffer(data)) { | |
| throw new TypeError("data should be a string, Buffer or Uint8Array"); | |
| } | |
| if (isFunction(encoding)) { | |
| callback = encoding; | |
| encoding = null; | |
| } | |
| // Ignore empty buffers, since writing them doesn't invoke the callback | |
| // https://github.com/nodejs/node/issues/22066 | |
| if (data.length === 0) { | |
| if (callback) { | |
| callback(); | |
| } | |
| return; | |
| } | |
| // Only write when we don't exceed the maximum body length | |
| if (this._requestBodyLength + data.length <= this._options.maxBodyLength) { | |
| this._requestBodyLength += data.length; | |
| this._requestBodyBuffers.push({ data: data, encoding: encoding }); | |
| this._currentRequest.write(data, encoding, callback); | |
| } | |
| // Error when we exceed the maximum body length | |
| else { | |
| this.emit("error", new MaxBodyLengthExceededError()); | |
| this.abort(); | |
| } | |
| }; | |
| // Ends the current native request | |
| RedirectableRequest.prototype.end = function (data, encoding, callback) { | |
| // Shift parameters if necessary | |
| if (isFunction(data)) { | |
| callback = data; | |
| data = encoding = null; | |
| } | |
| else if (isFunction(encoding)) { | |
| callback = encoding; | |
| encoding = null; | |
| } | |
| // Write data if needed and end | |
| if (!data) { | |
| this._ended = this._ending = true; | |
| this._currentRequest.end(null, null, callback); | |
| } | |
| else { | |
| var self = this; | |
| var currentRequest = this._currentRequest; | |
| this.write(data, encoding, function () { | |
| self._ended = true; | |
| currentRequest.end(null, null, callback); | |
| }); | |
| this._ending = true; | |
| } | |
| }; | |
| // Sets a header value on the current native request | |
| RedirectableRequest.prototype.setHeader = function (name, value) { | |
| this._options.headers[name] = value; | |
| this._currentRequest.setHeader(name, value); | |
| }; | |
| // Clears a header value on the current native request | |
| RedirectableRequest.prototype.removeHeader = function (name) { | |
| delete this._options.headers[name]; | |
| this._currentRequest.removeHeader(name); | |
| }; | |
| // Global timeout for all underlying requests | |
| RedirectableRequest.prototype.setTimeout = function (msecs, callback) { | |
| var self = this; | |
| // Destroys the socket on timeout | |
| function destroyOnTimeout(socket) { | |
| socket.setTimeout(msecs); | |
| socket.removeListener("timeout", socket.destroy); | |
| socket.addListener("timeout", socket.destroy); | |
| } | |
| // Sets up a timer to trigger a timeout event | |
| function startTimer(socket) { | |
| if (self._timeout) { | |
| clearTimeout(self._timeout); | |
| } | |
| self._timeout = setTimeout(function () { | |
| self.emit("timeout"); | |
| clearTimer(); | |
| }, msecs); | |
| destroyOnTimeout(socket); | |
| } | |
| // Stops a timeout from triggering | |
| function clearTimer() { | |
| // Clear the timeout | |
| if (self._timeout) { | |
| clearTimeout(self._timeout); | |
| self._timeout = null; | |
| } | |
| // Clean up all attached listeners | |
| self.removeListener("abort", clearTimer); | |
| self.removeListener("error", clearTimer); | |
| self.removeListener("response", clearTimer); | |
| self.removeListener("close", clearTimer); | |
| if (callback) { | |
| self.removeListener("timeout", callback); | |
| } | |
| if (!self.socket) { | |
| self._currentRequest.removeListener("socket", startTimer); | |
| } | |
| } | |
| // Attach callback if passed | |
| if (callback) { | |
| this.on("timeout", callback); | |
| } | |
| // Start the timer if or when the socket is opened | |
| if (this.socket) { | |
| startTimer(this.socket); | |
| } | |
| else { | |
| this._currentRequest.once("socket", startTimer); | |
| } | |
| // Clean up on events | |
| this.on("socket", destroyOnTimeout); | |
| this.on("abort", clearTimer); | |
| this.on("error", clearTimer); | |
| this.on("response", clearTimer); | |
| this.on("close", clearTimer); | |
| return this; | |
| }; | |
| // Proxy all other public ClientRequest methods | |
| [ | |
| "flushHeaders", "getHeader", | |
| "setNoDelay", "setSocketKeepAlive", | |
| ].forEach(function (method) { | |
| RedirectableRequest.prototype[method] = function (a, b) { | |
| return this._currentRequest[method](a, b); | |
| }; | |
| }); | |
| // Proxy all public ClientRequest properties | |
| ["aborted", "connection", "socket"].forEach(function (property) { | |
| Object.defineProperty(RedirectableRequest.prototype, property, { | |
| get: function () { return this._currentRequest[property]; }, | |
| }); | |
| }); | |
| RedirectableRequest.prototype._sanitizeOptions = function (options) { | |
| // Ensure headers are always present | |
| if (!options.headers) { | |
| options.headers = {}; | |
| } | |
| // Since http.request treats host as an alias of hostname, | |
| // but the url module interprets host as hostname plus port, | |
| // eliminate the host property to avoid confusion. | |
| if (options.host) { | |
| // Use hostname if set, because it has precedence | |
| if (!options.hostname) { | |
| options.hostname = options.host; | |
| } | |
| delete options.host; | |
| } | |
| // Complete the URL object when necessary | |
| if (!options.pathname && options.path) { | |
| var searchPos = options.path.indexOf("?"); | |
| if (searchPos < 0) { | |
| options.pathname = options.path; | |
| } | |
| else { | |
| options.pathname = options.path.substring(0, searchPos); | |
| options.search = options.path.substring(searchPos); | |
| } | |
| } | |
| }; | |
| // Executes the next native request (initial or redirect) | |
| RedirectableRequest.prototype._performRequest = function () { | |
| // Load the native protocol | |
| var protocol = this._options.protocol; | |
| var nativeProtocol = this._options.nativeProtocols[protocol]; | |
| if (!nativeProtocol) { | |
| throw new TypeError("Unsupported protocol " + protocol); | |
| } | |
| // If specified, use the agent corresponding to the protocol | |
| // (HTTP and HTTPS use different types of agents) | |
| if (this._options.agents) { | |
| var scheme = protocol.slice(0, -1); | |
| this._options.agent = this._options.agents[scheme]; | |
| } | |
| // Create the native request and set up its event handlers | |
| var request = this._currentRequest = | |
| nativeProtocol.request(this._options, this._onNativeResponse); | |
| request._redirectable = this; | |
| for (var event of events) { | |
| request.on(event, eventHandlers[event]); | |
| } | |
| // RFC7230§5.3.1: When making a request directly to an origin server, […] | |
| // a client MUST send only the absolute path […] as the request-target. | |
| this._currentUrl = /^\//.test(this._options.path) ? | |
| url.format(this._options) : | |
| // When making a request to a proxy, […] | |
| // a client MUST send the target URI in absolute-form […]. | |
| this._options.path; | |
| // End a redirected request | |
| // (The first request must be ended explicitly with RedirectableRequest#end) | |
| if (this._isRedirect) { | |
| // Write the request entity and end | |
| var i = 0; | |
| var self = this; | |
| var buffers = this._requestBodyBuffers; | |
| (function writeNext(error) { | |
| // Only write if this request has not been redirected yet | |
| // istanbul ignore else | |
| if (request === self._currentRequest) { | |
| // Report any write errors | |
| // istanbul ignore if | |
| if (error) { | |
| self.emit("error", error); | |
| } | |
| // Write the next buffer if there are still left | |
| else if (i < buffers.length) { | |
| var buffer = buffers[i++]; | |
| // istanbul ignore else | |
| if (!request.finished) { | |
| request.write(buffer.data, buffer.encoding, writeNext); | |
| } | |
| } | |
| // End the request if `end` has been called on us | |
| else if (self._ended) { | |
| request.end(); | |
| } | |
| } | |
| }()); | |
| } | |
| }; | |
| // Processes a response from the current native request | |
| RedirectableRequest.prototype._processResponse = function (response) { | |
| // Store the redirected response | |
| var statusCode = response.statusCode; | |
| if (this._options.trackRedirects) { | |
| this._redirects.push({ | |
| url: this._currentUrl, | |
| headers: response.headers, | |
| statusCode: statusCode, | |
| }); | |
| } | |
| // RFC7231§6.4: The 3xx (Redirection) class of status code indicates | |
| // that further action needs to be taken by the user agent in order to | |
| // fulfill the request. If a Location header field is provided, | |
| // the user agent MAY automatically redirect its request to the URI | |
| // referenced by the Location field value, | |
| // even if the specific status code is not understood. | |
| // If the response is not a redirect; return it as-is | |
| var location = response.headers.location; | |
| if (!location || this._options.followRedirects === false || | |
| statusCode < 300 || statusCode >= 400) { | |
| response.responseUrl = this._currentUrl; | |
| response.redirects = this._redirects; | |
| this.emit("response", response); | |
| // Clean up | |
| this._requestBodyBuffers = []; | |
| return; | |
| } | |
| // The response is a redirect, so abort the current request | |
| destroyRequest(this._currentRequest); | |
| // Discard the remainder of the response to avoid waiting for data | |
| response.destroy(); | |
| // RFC7231§6.4: A client SHOULD detect and intervene | |
| // in cyclical redirections (i.e., "infinite" redirection loops). | |
| if (++this._redirectCount > this._options.maxRedirects) { | |
| throw new TooManyRedirectsError(); | |
| } | |
| // Store the request headers if applicable | |
| var requestHeaders; | |
| var beforeRedirect = this._options.beforeRedirect; | |
| if (beforeRedirect) { | |
| requestHeaders = Object.assign({ | |
| // The Host header was set by nativeProtocol.request | |
| Host: response.req.getHeader("host"), | |
| }, this._options.headers); | |
| } | |
| // RFC7231§6.4: Automatic redirection needs to done with | |
| // care for methods not known to be safe, […] | |
| // RFC7231§6.4.2–3: For historical reasons, a user agent MAY change | |
| // the request method from POST to GET for the subsequent request. | |
| var method = this._options.method; | |
| if ((statusCode === 301 || statusCode === 302) && this._options.method === "POST" || | |
| // RFC7231§6.4.4: The 303 (See Other) status code indicates that | |
| // the server is redirecting the user agent to a different resource […] | |
| // A user agent can perform a retrieval request targeting that URI | |
| // (a GET or HEAD request if using HTTP) […] | |
| (statusCode === 303) && !/^(?:GET|HEAD)$/.test(this._options.method)) { | |
| this._options.method = "GET"; | |
| // Drop a possible entity and headers related to it | |
| this._requestBodyBuffers = []; | |
| removeMatchingHeaders(/^content-/i, this._options.headers); | |
| } | |
| // Drop the Host header, as the redirect might lead to a different host | |
| var currentHostHeader = removeMatchingHeaders(/^host$/i, this._options.headers); | |
| // If the redirect is relative, carry over the host of the last request | |
| var currentUrlParts = parseUrl(this._currentUrl); | |
| var currentHost = currentHostHeader || currentUrlParts.host; | |
| var currentUrl = /^\w+:/.test(location) ? this._currentUrl : | |
| url.format(Object.assign(currentUrlParts, { host: currentHost })); | |
| // Create the redirected request | |
| var redirectUrl = resolveUrl(location, currentUrl); | |
| debug("redirecting to", redirectUrl.href); | |
| this._isRedirect = true; | |
| spreadUrlObject(redirectUrl, this._options); | |
| // Drop confidential headers when redirecting to a less secure protocol | |
| // or to a different domain that is not a superdomain | |
| if (redirectUrl.protocol !== currentUrlParts.protocol && | |
| redirectUrl.protocol !== "https:" || | |
| redirectUrl.host !== currentHost && | |
| !isSubdomain(redirectUrl.host, currentHost)) { | |
| removeMatchingHeaders(/^(?:(?:proxy-)?authorization|cookie)$/i, this._options.headers); | |
| } | |
| // Evaluate the beforeRedirect callback | |
| if (isFunction(beforeRedirect)) { | |
| var responseDetails = { | |
| headers: response.headers, | |
| statusCode: statusCode, | |
| }; | |
| var requestDetails = { | |
| url: currentUrl, | |
| method: method, | |
| headers: requestHeaders, | |
| }; | |
| beforeRedirect(this._options, responseDetails, requestDetails); | |
| this._sanitizeOptions(this._options); | |
| } | |
| // Perform the redirected request | |
| this._performRequest(); | |
| }; | |
| // Wraps the key/value object of protocols with redirect functionality | |
| function wrap(protocols) { | |
| // Default settings | |
| var exports = { | |
| maxRedirects: 21, | |
| maxBodyLength: 10 * 1024 * 1024, | |
| }; | |
| // Wrap each protocol | |
| var nativeProtocols = {}; | |
| Object.keys(protocols).forEach(function (scheme) { | |
| var protocol = scheme + ":"; | |
| var nativeProtocol = nativeProtocols[protocol] = protocols[scheme]; | |
| var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol); | |
| // Executes a request, following redirects | |
| function request(input, options, callback) { | |
| // Parse parameters, ensuring that input is an object | |
| if (isURL(input)) { | |
| input = spreadUrlObject(input); | |
| } | |
| else if (isString(input)) { | |
| input = spreadUrlObject(parseUrl(input)); | |
| } | |
| else { | |
| callback = options; | |
| options = validateUrl(input); | |
| input = { protocol: protocol }; | |
| } | |
| if (isFunction(options)) { | |
| callback = options; | |
| options = null; | |
| } | |
| // Set defaults | |
| options = Object.assign({ | |
| maxRedirects: exports.maxRedirects, | |
| maxBodyLength: exports.maxBodyLength, | |
| }, input, options); | |
| options.nativeProtocols = nativeProtocols; | |
| if (!isString(options.host) && !isString(options.hostname)) { | |
| options.hostname = "::1"; | |
| } | |
| assert.equal(options.protocol, protocol, "protocol mismatch"); | |
| debug("options", options); | |
| return new RedirectableRequest(options, callback); | |
| } | |
| // Executes a GET request, following redirects | |
| function get(input, options, callback) { | |
| var wrappedRequest = wrappedProtocol.request(input, options, callback); | |
| wrappedRequest.end(); | |
| return wrappedRequest; | |
| } | |
| // Expose the properties on the wrapped protocol | |
| Object.defineProperties(wrappedProtocol, { | |
| request: { value: request, configurable: true, enumerable: true, writable: true }, | |
| get: { value: get, configurable: true, enumerable: true, writable: true }, | |
| }); | |
| }); | |
| return exports; | |
| } | |
| function noop() { /* empty */ } | |
| function parseUrl(input) { | |
| var parsed; | |
| // istanbul ignore else | |
| if (useNativeURL) { | |
| parsed = new URL(input); | |
| } | |
| else { | |
| // Ensure the URL is valid and absolute | |
| parsed = validateUrl(url.parse(input)); | |
| if (!isString(parsed.protocol)) { | |
| throw new InvalidUrlError({ input }); | |
| } | |
| } | |
| return parsed; | |
| } | |
| function resolveUrl(relative, base) { | |
| // istanbul ignore next | |
| return useNativeURL ? new URL(relative, base) : parseUrl(url.resolve(base, relative)); | |
| } | |
| function validateUrl(input) { | |
| if (/^\[/.test(input.hostname) && !/^\[[:0-9a-f]+\]$/i.test(input.hostname)) { | |
| throw new InvalidUrlError({ input: input.href || input }); | |
| } | |
| if (/^\[/.test(input.host) && !/^\[[:0-9a-f]+\](:\d+)?$/i.test(input.host)) { | |
| throw new InvalidUrlError({ input: input.href || input }); | |
| } | |
| return input; | |
| } | |
| function spreadUrlObject(urlObject, target) { | |
| var spread = target || {}; | |
| for (var key of preservedUrlFields) { | |
| spread[key] = urlObject[key]; | |
| } | |
| // Fix IPv6 hostname | |
| if (spread.hostname.startsWith("[")) { | |
| spread.hostname = spread.hostname.slice(1, -1); | |
| } | |
| // Ensure port is a number | |
| if (spread.port !== "") { | |
| spread.port = Number(spread.port); | |
| } | |
| // Concatenate path | |
| spread.path = spread.search ? spread.pathname + spread.search : spread.pathname; | |
| return spread; | |
| } | |
| function removeMatchingHeaders(regex, headers) { | |
| var lastValue; | |
| for (var header in headers) { | |
| if (regex.test(header)) { | |
| lastValue = headers[header]; | |
| delete headers[header]; | |
| } | |
| } | |
| return (lastValue === null || typeof lastValue === "undefined") ? | |
| undefined : String(lastValue).trim(); | |
| } | |
| function createErrorType(code, message, baseClass) { | |
| // Create constructor | |
| function CustomError(properties) { | |
| // istanbul ignore else | |
| if (isFunction(Error.captureStackTrace)) { | |
| Error.captureStackTrace(this, this.constructor); | |
| } | |
| Object.assign(this, properties || {}); | |
| this.code = code; | |
| this.message = this.cause ? message + ": " + this.cause.message : message; | |
| } | |
| // Attach constructor and set default properties | |
| CustomError.prototype = new (baseClass || Error)(); | |
| Object.defineProperties(CustomError.prototype, { | |
| constructor: { | |
| value: CustomError, | |
| enumerable: false, | |
| }, | |
| name: { | |
| value: "Error [" + code + "]", | |
| enumerable: false, | |
| }, | |
| }); | |
| return CustomError; | |
| } | |
| function destroyRequest(request, error) { | |
| for (var event of events) { | |
| request.removeListener(event, eventHandlers[event]); | |
| } | |
| request.on("error", noop); | |
| request.destroy(error); | |
| } | |
| function isSubdomain(subdomain, domain) { | |
| assert(isString(subdomain) && isString(domain)); | |
| var dot = subdomain.length - domain.length - 1; | |
| return dot > 0 && subdomain[dot] === "." && subdomain.endsWith(domain); | |
| } | |
| function isString(value) { | |
| return typeof value === "string" || value instanceof String; | |
| } | |
| function isFunction(value) { | |
| return typeof value === "function"; | |
| } | |
| function isBuffer(value) { | |
| return typeof value === "object" && ("length" in value); | |
| } | |
| function isURL(value) { | |
| return URL && value instanceof URL; | |
| } | |
| // Exports | |
| module.exports = wrap({ http: http, https: https }); | |
| module.exports.wrap = wrap; | |
Xet Storage Details
- Size:
- 20.6 kB
- Xet hash:
- 26b2d956782f0d6c5d16d2e6df26c1b3f24e8acba3ffd55f99f55e41f4e40705
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.