test('check for pipe method', withServer, (t, server, got) => { server.get('/', defaultHandler); const stream = got.stream(''); t.true(is.function_(stream.pipe)); t.true(is.function_(stream.on('foobar', () => {}).pipe)); stream.destroy(); });
export default (response: IncomingMessage, options: Options, emitter: EventEmitter) => { const downloadBodySize = Number(response.headers['content-length']) || undefined; const progressStream: TransformStream = downloadProgress(response, emitter, downloadBodySize); mimicResponse(response, progressStream); const newResponse = ( options.decompress === true && is.function_(decompressResponse) && options.method !== 'HEAD' ? decompressResponse(progressStream as unknown as IncomingMessage) : progressStream ) as Response; if (!options.decompress && ['gzip', 'deflate', 'br'].includes(response.headers['content-encoding'] || '')) { options.encoding = null; } emitter.emit('response', newResponse); emitter.emit('downloadProgress', { percent: 0, transferred: 0, total: downloadBodySize }); response.pipe(progressStream); };
get: (target, name) => { if (name === 'trailers' || name === 'rawTrailers') { return []; } const value = target[name]; return is.function_(value) ? value.bind(target) : value; }
export function findScripts( scriptPath: string, config: ResolvedDebugAgentConfig, fileStats: ScanStats, logger: consoleLogLevel.Logger ): string[] { // (path: string, knownFiles: string[], resolved: string[]) => string[] const resolved = resolveScripts(scriptPath, config, fileStats); if (config.pathResolver) { if (!is.function_(config.pathResolver)) { logger.warn( `The 'pathResolver' config must be a function. Continuing ` + `with the agent's default behavior.` ); return resolved; } const knownFiles = Object.keys(fileStats); const calculatedPaths = config.pathResolver( scriptPath, knownFiles, resolved ); if (calculatedPaths === undefined) { return resolved; } if (!calculatedPaths || !Array.isArray(calculatedPaths)) { logger.warn( `The 'pathResolver' config function returned a value ` + `other than 'undefined' or an array of strings. Continuing with ` + `the agent's default behavior.` ); return resolved; } for (const path of calculatedPaths) { if (knownFiles.indexOf(path) === -1) { logger.warn( `The 'pathResolver' config function returned a path ` + `'${path}' that is not in the list of paths known to the debug agent ` + JSON.stringify(knownFiles, null, 2) + ` only known paths can be returned. Continuing with the agent's ` + `default behavior.` ); return resolved; } } return calculatedPaths; } return resolved; }
export default (body: unknown): body is FormData => is.nodeStream(body) && is.function_((body as FormData).getBoundary);
const get = async (options: Options) => { const currentUrl = redirectString || requestUrl; if (options.protocol !== 'http:' && options.protocol !== 'https:') { throw new UnsupportedProtocolError(options); } decodeURI(currentUrl); let requestFn: RequestFunction; if (is.function_(options.request)) { requestFn = options.request; } else { requestFn = options.protocol === 'https:' ? https.request : http.request; } if (agents) { const protocolName = options.protocol === 'https:' ? 'https' : 'http'; options.agent = agents[protocolName] || options.agent; } /* istanbul ignore next: electron.net is broken */ // No point in typing process.versions correctly, as // process.version.electron is used only once, right here. if (options.useElectronNet && (process.versions as any).electron) { // @ts-ignore const electron = dynamicRequire(module, 'electron') as any; // Trick webpack requestFn = electron.net.request || electron.remote.net.request; } if (options.cookieJar) { const cookieString = await getCookieString(currentUrl, {}); if (is.nonEmptyString(cookieString)) { options.headers.cookie = cookieString; } } let timings: Timings; // TODO: Properly type this. const handleResponse = async response => { try { /* istanbul ignore next: fixes https://github.com/electron/electron/blob/cbb460d47628a7a146adf4419ed48550a98b2923/lib/browser/api/net.js#L59-L65 */ if (options.useElectronNet) { response = new Proxy(response, { get: (target, name) => { if (name === 'trailers' || name === 'rawTrailers') { return []; } const value = target[name]; return is.function_(value) ? value.bind(target) : value; } }); } const {statusCode} = response; response.statusMessage = response.statusMessage || http.STATUS_CODES[statusCode]; response.url = currentUrl; response.requestUrl = requestUrl; response.retryCount = retryCount; response.timings = timings; response.redirectUrls = redirects; response.request = {options}; response.isFromCache = response.fromCache || false; delete response.fromCache; const rawCookies = response.headers['set-cookie']; if (options.cookieJar && rawCookies) { await Promise.all(rawCookies.map(rawCookie => setCookie(rawCookie, response.url))); } if (options.followRedirect && 'location' in response.headers) { if (allMethodRedirectCodes.has(statusCode) || (getMethodRedirectCodes.has(statusCode) && (options.method === 'GET' || options.method === 'HEAD'))) { response.resume(); // We're being redirected, we don't care about the response. if (statusCode === 303) { // Server responded with "see other", indicating that the resource exists at another location, // and the client should request it from that location via GET or HEAD. options.method = 'GET'; } if (redirects.length >= 10) { throw new MaxRedirectsError(response, options); } // Handles invalid URLs. See https://github.com/sindresorhus/got/issues/604 const redirectBuffer = Buffer.from(response.headers.location, 'binary').toString(); const redirectURL = new URL(redirectBuffer, currentUrl); redirectString = redirectURL.toString(); redirects.push(redirectString); const redirectOptions = { ...options, port: null, auth: null, ...urlToOptions(redirectURL) }; for (const hook of options.hooks.beforeRedirect) { // eslint-disable-next-line no-await-in-loop await hook(redirectOptions); } emitter.emit('redirect', response, redirectOptions); await get(redirectOptions); return; } } getResponse(response, options, emitter); } catch (error) { emitError(error); } }; const handleRequest = (request: http.ClientRequest) => { if (shouldAbort) { request.abort(); return; } currentRequest = request; request.on('error', error => { if (request.aborted || error.message === 'socket hang up') { return; } if (error instanceof TimedOutTimeoutError) { error = new TimeoutError(error, timings, options); } else { error = new RequestError(error, options); } if (emitter.retry(error) === false) { emitError(error); } }); timings = timer(request); uploadProgress(request, emitter, uploadBodySize); if (options.gotTimeout) { // TODO: Properly type this. `preNormalizeArguments` coerces `gotTimeout` to `Delays`. timedOut(request, options.gotTimeout as Delays, options); } emitter.emit('request', request); const uploadComplete = () => { request.emit('upload-complete'); }; try { if (is.nodeStream(options.body)) { options.body.once('end', uploadComplete); options.body.pipe(request); options.body = undefined; } else if (options.body) { request.end(options.body, uploadComplete); } else if (input && (options.method === 'POST' || options.method === 'PUT' || options.method === 'PATCH')) { input.once('end', uploadComplete); input.pipe(request); } else { request.end(uploadComplete); } } catch (error) { emitError(new RequestError(error, options)); } }; if (options.cache) { const cacheableRequest = new CacheableRequest(requestFn, options.cache); // TODO: Properly type this. const cacheRequest = cacheableRequest(options as https.RequestOptions, handleResponse); cacheRequest.once('error', error => { if (error instanceof CacheableRequest.RequestError) { emitError(new RequestError(error, options)); } else { emitError(new CacheError(error, options)); } }); cacheRequest.once('request', handleRequest); } else { // Catches errors thrown by calling requestFn(...) try { // TODO: Properly type this. handleRequest(requestFn(options as https.RequestOptions, handleResponse)); } catch (error) { emitError(new RequestError(error, options)); } } };
export const normalizeArguments = (url, options, defaults?: any) => { if (is.plainObject(url)) { options = {...url, ...options}; url = options.url || ''; delete options.url; } if (defaults) { options = merge({}, defaults.options, options ? preNormalizeArguments(options, defaults.options) : {}); } else { options = merge({}, preNormalizeArguments(options)); } if (!is.string(url) && !is.object(url)) { throw new TypeError(`Parameter \`url\` must be a string or object, not ${is(url)}`); } if (is.string(url) && !(url === '' && !options.baseUrl)) { if (options.baseUrl) { if (url.startsWith('/')) { url = url.slice(1); } } else { url = url.replace(/^unix:/, 'http://$&'); } url = urlToOptions(new URL(url, options.baseUrl)); } else if (is(url) === 'URL') { url = urlToOptions(url); } // Override both null/undefined with default protocol options = merge({path: ''}, url, {protocol: url.protocol || 'https:'}, options); for (const hook of options.hooks.init) { const called = hook(options); if (is.promise(called)) { throw new TypeError('The `init` hook must be a synchronous function'); } } const {baseUrl} = options; Object.defineProperty(options, 'baseUrl', { set: () => { throw new Error('Failed to set baseUrl. Options are normalized already.'); }, get: () => baseUrl }); let {searchParams} = options; delete options.searchParams; if (options.query) { if (!shownDeprecation) { console.warn('`options.query` is deprecated. We support it solely for compatibility - it will be removed in Got 11. Use `options.searchParams` instead.'); shownDeprecation = true; } searchParams = options.query; delete options.query; } // TODO: This should be used in the `options` type instead interface SearchParams { [key: string]: string | number | boolean | null; } if (is.nonEmptyString(searchParams) || is.nonEmptyObject(searchParams) || searchParams instanceof URLSearchParams) { if (!is.string(searchParams)) { if (!(searchParams instanceof URLSearchParams)) { validateSearchParams(searchParams); searchParams = searchParams as SearchParams; } searchParams = (new URLSearchParams(searchParams)).toString(); } options.path = `${options.path.split('?')[0]}?${searchParams}`; } if (options.hostname === 'unix') { const matches = /(.+?):(.+)/.exec(options.path); if (matches) { const [, socketPath, path] = matches; options = { ...options, socketPath, path, host: null }; } } const {headers} = options; for (const [key, value] of Object.entries(headers)) { if (is.nullOrUndefined(value)) { delete headers[key]; } } if (options.decompress && is.undefined(headers['accept-encoding'])) { headers['accept-encoding'] = supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate'; } if (options.method) { options.method = options.method.toUpperCase(); } if (!is.function_(options.retry.retries)) { const {retries} = options.retry; options.retry.retries = (iteration, error) => { if (iteration > retries) { return 0; } const hasCode = Reflect.has(error, 'code') && options.retry.errorCodes.has(error.code); const hasMethod = Reflect.has(error, 'options') && options.retry.methods.has(error.options.method); const hasStatusCode = Reflect.has(error, 'response') && options.retry.statusCodes.has(error.response.statusCode); if ((!error || !hasCode) && (!hasMethod || !hasStatusCode)) { return 0; } const {response} = error; if (response && Reflect.has(response.headers, 'retry-after') && retryAfterStatusCodes.has(response.statusCode)) { let after = Number(response.headers['retry-after']); if (is.nan(after)) { after = Date.parse(response.headers['retry-after']) - Date.now(); } else { after *= 1000; } if (after > options.retry.maxRetryAfter) { return 0; } return after; } if (response && response.statusCode === 413) { return 0; } const noise = Math.random() * 100; return ((2 ** (iteration - 1)) * 1000) + noise; }; } return options; };