pax_global_header00006660000000000000000000000064142434735200014516gustar00rootroot0000000000000052 comment=5e17bb748c260b02e4cf716c2f4079a1c6a7481e got-11.8.5/000077500000000000000000000000001424347352000124035ustar00rootroot00000000000000got-11.8.5/.editorconfig000066400000000000000000000002571424347352000150640ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 got-11.8.5/.gitattributes000066400000000000000000000000371424347352000152760ustar00rootroot00000000000000* text=auto eol=lf *.ai binary got-11.8.5/.github/000077500000000000000000000000001424347352000137435ustar00rootroot00000000000000got-11.8.5/.github/ISSUE_TEMPLATE/000077500000000000000000000000001424347352000161265ustar00rootroot00000000000000got-11.8.5/.github/ISSUE_TEMPLATE/1-bug-report.md000066400000000000000000000013341424347352000206750ustar00rootroot00000000000000--- name: "🐞 Bug report" about: Something is not working as it should --- #### Describe the bug - Node.js version: - OS & version: #### Actual behavior ... #### Expected behavior ... #### Code to reproduce ```js ... ``` #### Checklist - [ ] I have read the documentation. - [ ] I have tried my code with the latest version of Node.js and Got. got-11.8.5/.github/ISSUE_TEMPLATE/2-feature-request.md000066400000000000000000000005671424347352000217400ustar00rootroot00000000000000--- name: "⭐ Feature request" about: Suggest an idea for Got --- #### What problem are you trying to solve? ... #### Describe the feature ... #### Checklist - [ ] I have read the documentation and made sure this feature doesn't already exist. got-11.8.5/.github/ISSUE_TEMPLATE/3-question.md000066400000000000000000000002631424347352000204600ustar00rootroot00000000000000--- name: "❓ Question" about: Something is unclear or needs to be discussed --- #### What would you like to discuss? ... #### Checklist - [ ] I have read the documentation. got-11.8.5/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000003741424347352000175500ustar00rootroot00000000000000#### Checklist - [ ] I have read the documentation. - [ ] I have included a pull request description of my changes. - [ ] I have included some tests. - [ ] If it's a new feature, I have included documentation updates in both the README and the types. got-11.8.5/.github/funding.yml000066400000000000000000000000641424347352000161200ustar00rootroot00000000000000github: [sindresorhus, szmarczak] tidelift: npm/got got-11.8.5/.github/security.md000066400000000000000000000002631424347352000161350ustar00rootroot00000000000000# Security Policy To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure. got-11.8.5/.gitignore000066400000000000000000000000611424347352000143700ustar00rootroot00000000000000node_modules yarn.lock coverage .nyc_output dist got-11.8.5/.npmrc000066400000000000000000000000231424347352000135160ustar00rootroot00000000000000package-lock=false got-11.8.5/.travis.yml000066400000000000000000000013701424347352000145150ustar00rootroot00000000000000language: node_js after_success: - './node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls' jobs: include: - os: linux dist: focal node_js: '14' - os: linux dist: focal node_js: '12' - os: linux dist: focal node_js: '10' - os: linux dist: bionic node_js: '14' - os: linux dist: bionic node_js: '12' - os: linux dist: bionic node_js: '10' - os: windows node_js: '14' - os: windows node_js: '12' - os: windows node_js: '10' - os: osx osx_image: xcode12 node_js: '14' - os: osx osx_image: xcode12 node_js: '12' - os: osx osx_image: xcode12 node_js: '10' got-11.8.5/benchmark/000077500000000000000000000000001424347352000143355ustar00rootroot00000000000000got-11.8.5/benchmark/index.ts000066400000000000000000000116621424347352000160220ustar00rootroot00000000000000'use strict'; import {URL} from 'url'; import https = require('https'); import axios from 'axios'; import Benchmark = require('benchmark'); import fetch from 'node-fetch'; import request = require('request'); import got from '../source'; import Request, {kIsNormalizedAlready} from '../source/core'; const {normalizeArguments} = Request; // Configuration const httpsAgent = new https.Agent({ keepAlive: true, rejectUnauthorized: false }); const url = new URL('https://127.0.0.1:8080'); const urlString = url.toString(); const gotOptions = { agent: { https: httpsAgent }, https: { rejectUnauthorized: false }, retry: 0 }; const normalizedGotOptions = normalizeArguments(url, gotOptions); normalizedGotOptions[kIsNormalizedAlready] = true; const requestOptions = { strictSSL: false, agent: httpsAgent }; const fetchOptions = { agent: httpsAgent }; const axiosOptions = { url: urlString, httpsAgent, https: { rejectUnauthorized: false } }; const axiosStreamOptions: typeof axiosOptions & {responseType: 'stream'} = { ...axiosOptions, responseType: 'stream' }; const httpsOptions = { https: { rejectUnauthorized: false }, agent: httpsAgent }; const suite = new Benchmark.Suite(); // Benchmarking suite.add('got - promise', { defer: true, fn: async (deferred: {resolve: () => void}) => { await got(url, gotOptions); deferred.resolve(); } }).add('got - stream', { defer: true, fn: async (deferred: {resolve: () => void}) => { got.stream(url, gotOptions).resume().once('end', () => { deferred.resolve(); }); } }).add('got - core', { defer: true, fn: async (deferred: {resolve: () => void}) => { const stream = new Request(url, gotOptions); stream.resume().once('end', () => { deferred.resolve(); }); } }).add('got - core - normalized options', { defer: true, fn: async (deferred: {resolve: () => void}) => { const stream = new Request(undefined as any, normalizedGotOptions); stream.resume().once('end', () => { deferred.resolve(); }); } }).add('request - callback', { defer: true, fn: (deferred: {resolve: () => void}) => { request(urlString, requestOptions, (error: Error) => { if (error) { throw error; } deferred.resolve(); }); } }).add('request - stream', { defer: true, fn: (deferred: {resolve: () => void}) => { const stream = request(urlString, requestOptions); stream.resume(); stream.once('end', () => { deferred.resolve(); }); } }).add('node-fetch - promise', { defer: true, fn: async (deferred: {resolve: () => void}) => { const response = await fetch(url, fetchOptions); await response.text(); deferred.resolve(); } }).add('node-fetch - stream', { defer: true, fn: async (deferred: {resolve: () => void}) => { const {body} = await fetch(url, fetchOptions); body.resume(); body.once('end', () => { deferred.resolve(); }); } }).add('axios - promise', { defer: true, fn: async (deferred: {resolve: () => void}) => { await axios.request(axiosOptions); deferred.resolve(); } }).add('axios - stream', { defer: true, fn: async (deferred: {resolve: () => void}) => { const {data} = await axios.request(axiosStreamOptions); data.resume(); data.once('end', () => { deferred.resolve(); }); } }).add('https - stream', { defer: true, fn: (deferred: {resolve: () => void}) => { https.request(urlString, httpsOptions, response => { response.resume(); response.once('end', () => { deferred.resolve(); }); }).end(); } }).on('cycle', (event: Benchmark.Event) => { console.log(String(event.target)); }).on('complete', function (this: any) { console.log(`Fastest is ${this.filter('fastest').map('name') as string}`); internalBenchmark(); }).run(); const internalBenchmark = (): void => { console.log(); const internalSuite = new Benchmark.Suite(); internalSuite.add('got - normalize options', { fn: () => { normalizeArguments(url, gotOptions); } }).on('cycle', (event: Benchmark.Event) => { console.log(String(event.target)); }); internalSuite.run(); }; // Results (i7-7700k, CPU governor: performance): // got - promise x 3,003 ops/sec ±6.26% (70 runs sampled) // got - stream x 3,538 ops/sec ±5.86% (67 runs sampled) // got - core x 5,828 ops/sec ±3.11% (79 runs sampled) // got - core - normalized options x 7,596 ops/sec ±1.60% (85 runs sampled) // request - callback x 6,530 ops/sec ±6.84% (72 runs sampled) // request - stream x 7,348 ops/sec ±3.62% (78 runs sampled) // node-fetch - promise x 6,284 ops/sec ±5.50% (76 runs sampled) // node-fetch - stream x 7,746 ops/sec ±3.32% (80 runs sampled) // axios - promise x 6,301 ops/sec ±6.24% (77 runs sampled) // axios - stream x 8,605 ops/sec ±2.73% (87 runs sampled) // https - stream x 10,477 ops/sec ±3.64% (80 runs sampled) // Fastest is https - stream // got - normalize options x 90,974 ops/sec ±0.57% (93 runs sampled) got-11.8.5/benchmark/server.ts000066400000000000000000000007131424347352000162140ustar00rootroot00000000000000import {AddressInfo} from 'net'; import https = require('https'); // @ts-expect-error No types import createCert = require('create-cert'); (async () => { const keys = await createCert({days: 365, commonName: 'localhost'}); const server = https.createServer(keys, (_request, response) => { response.end('ok'); }).listen(8080, () => { const {port} = server.address() as AddressInfo; console.log(`Listening at https://localhost:${port}`); }); })(); got-11.8.5/documentation/000077500000000000000000000000001424347352000152545ustar00rootroot00000000000000got-11.8.5/documentation/advanced-creation.md000066400000000000000000000067351424347352000211600ustar00rootroot00000000000000# Advanced creation > Make calling REST APIs easier by creating niche-specific `got` instances. ### Merging instances Got supports composing multiple instances together. This is very powerful. You can create a client that limits download speed and then compose it with an instance that signs a request. It's like plugins without any of the plugin mess. You just create instances and then compose them together. To mix them use `instanceA.extend(instanceB, instanceC, ...)`, that's all. ## Examples Some examples of what kind of instances you could compose together: #### Denying redirects that lead to other sites than specified ```js const controlRedirects = got.extend({ handlers: [ (options, next) => { const promiseOrStream = next(options); return promiseOrStream.on('redirect', response => { const host = new URL(resp.url).host; if (options.allowedHosts && !options.allowedHosts.includes(host)) { promiseOrStream.cancel(`Redirection to ${host} is not allowed`); } }); } ] }); ``` #### Limiting download & upload size It can be useful when your machine has limited amount of memory. ```js const limitDownloadUpload = got.extend({ handlers: [ (options, next) => { let promiseOrStream = next(options); if (typeof options.downloadLimit === 'number') { promiseOrStream.on('downloadProgress', progress => { if (progress.transferred > options.downloadLimit && progress.percent !== 1) { promiseOrStream.cancel(`Exceeded the download limit of ${options.downloadLimit} bytes`); } }); } if (typeof options.uploadLimit === 'number') { promiseOrStream.on('uploadProgress', progress => { if (progress.transferred > options.uploadLimit && progress.percent !== 1) { promiseOrStream.cancel(`Exceeded the upload limit of ${options.uploadLimit} bytes`); } }); } return promiseOrStream; } ] }); ``` #### No user agent ```js const noUserAgent = got.extend({ headers: { 'user-agent': undefined } }); ``` #### Custom endpoint ```js const httpbin = got.extend({ prefixUrl: 'https://httpbin.org/' }); ``` #### Signing requests ```js const crypto = require('crypto'); const getMessageSignature = (data, secret) => crypto.createHmac('sha256', secret).update(data).digest('hex').toUpperCase(); const signRequest = got.extend({ hooks: { beforeRequest: [ options => { options.headers['sign'] = getMessageSignature(options.body || '', process.env.SECRET); } ] } }); ``` #### Putting it all together If these instances are different modules and you don't want to rewrite them, use `got.extend(...instances)`. **Note**: The `noUserAgent` instance must be placed at the end of chain as the instances are merged in order. Other instances do have the `user-agent` header. ```js const merged = got.extend(controlRedirects, limitDownloadUpload, httpbin, signRequest, noUserAgent); (async () => { // There's no 'user-agent' header :) await merged('/'); /* HTTP Request => * GET / HTTP/1.1 * accept-encoding: gzip, deflate, br * sign: F9E66E179B6747AE54108F82F8ADE8B3C25D76FD30AFDE6C395822C530196169 * Host: httpbin.org * Connection: close */ const MEGABYTE = 1048576; await merged('https://ipv4.download.thinkbroadband.com/5MB.zip', {downloadLimit: MEGABYTE, prefixUrl: ''}); // CancelError: Exceeded the download limit of 1048576 bytes await merged('https://jigsaw.w3.org/HTTP/300/301.html', {allowedHosts: ['google.com'], prefixUrl: ''}); // CancelError: Redirection to jigsaw.w3.org is not allowed })(); ``` got-11.8.5/documentation/examples/000077500000000000000000000000001424347352000170725ustar00rootroot00000000000000got-11.8.5/documentation/examples/gh-got.js000066400000000000000000000026711424347352000206230ustar00rootroot00000000000000'use strict'; const got = require('../..'); const package = require('../../package'); const getRateLimit = (headers) => ({ limit: parseInt(headers['x-ratelimit-limit'], 10), remaining: parseInt(headers['x-ratelimit-remaining'], 10), reset: new Date(parseInt(headers['x-ratelimit-reset'], 10) * 1000) }); const instance = got.extend({ prefixUrl: 'https://api.github.com', headers: { accept: 'application/vnd.github.v3+json', 'user-agent': `${package.name}/${package.version}` }, responseType: 'json', token: process.env.GITHUB_TOKEN, handlers: [ (options, next) => { // Authorization if (options.token && !options.headers.authorization) { options.headers.authorization = `token ${options.token}`; } // Don't touch streams if (options.isStream) { return next(options); } // Magic begins return (async () => { try { const response = await next(options); // Rate limit for the Response object response.rateLimit = getRateLimit(response.headers); return response; } catch (error) { const {response} = error; // Nicer errors if (response && response.body) { error.name = 'GitHubError'; error.message = `${response.body.message} (${response.statusCode} status code)`; } // Rate limit for errors if (response) { error.rateLimit = getRateLimit(response.headers); } throw error; } })(); } ] }); module.exports = instance; got-11.8.5/documentation/examples/runkit-example.js000066400000000000000000000003731424347352000224000ustar00rootroot00000000000000const got = require('got'); (async () => { const issUrl = 'http://api.open-notify.org/iss-now.json'; const {iss_position: issPosition} = await got(issUrl).json(); console.log(issPosition); //=> {latitude: '20.4956', longitude: '42.2216'} })(); got-11.8.5/documentation/lets-make-a-plugin.md000066400000000000000000000151431424347352000211760ustar00rootroot00000000000000# Let's make a plugin! > Another example on how to use Got like a boss :electric_plug: Okay, so you already have learned some basics. That's great! When it comes to advanced usage, custom instances are really helpful. For example, take a look at [`gh-got`](https://github.com/sindresorhus/gh-got). It looks pretty complicated, but... it's really not. Before we start, we need to find the [GitHub API docs](https://developer.github.com/v3/). Let's write down the most important information: 1. The root endpoint is `https://api.github.com/`. 2. We will use version 3 of the API.\ The `Accept` header needs to be set to `application/vnd.github.v3+json`. 3. The body is in a JSON format. 4. We will use OAuth2 for authorization. 5. We may receive `400 Bad Request` or `422 Unprocessable Entity`.\ The body contains detailed information about the error. 6. *Pagination?* Not yet. This is going to be a native feature of Got. We'll update this page accordingly when the feature is available. 7. Rate limiting. These headers are interesting: - `X-RateLimit-Limit` - `X-RateLimit-Remaining` - `X-RateLimit-Reset` - `X-GitHub-Request-Id` Also `X-GitHub-Request-Id` may be useful. 8. User-Agent is required. When we have all the necessary info, we can start mixing :cake: ### The root endpoint Not much to do here, just extend an instance and provide the `prefixUrl` option: ```js const got = require('got'); const instance = got.extend({ prefixUrl: 'https://api.github.com' }); module.exports = instance; ``` ### v3 API GitHub needs to know which version we are using. We'll use the `Accept` header for that: ```js const got = require('got'); const instance = got.extend({ prefixUrl: 'https://api.github.com', headers: { accept: 'application/vnd.github.v3+json' } }); module.exports = instance; ``` ### JSON body We'll use [`options.responseType`](../readme.md#responsetype): ```js const got = require('got'); const instance = got.extend({ prefixUrl: 'https://api.github.com', headers: { accept: 'application/vnd.github.v3+json' }, responseType: 'json' }); module.exports = instance; ``` ### Authorization It's common to set some environment variables, for example, `GITHUB_TOKEN`. You can modify the tokens in all your apps easily, right? Cool. What about... we want to provide a unique token for each app. Then we will need to create a new option - it will default to the environment variable, but you can easily override it. Let's use handlers instead of hooks. This will make our code more readable: having `beforeRequest`, `beforeError` and `afterResponse` hooks for just a few lines of code would complicate things unnecessarily. **Tip:** it's a good practice to use hooks when your plugin gets complicated. Try not to overload the handler function, but don't abuse hooks either. ```js const got = require('got'); const instance = got.extend({ prefixUrl: 'https://api.github.com', headers: { accept: 'application/vnd.github.v3+json' }, responseType: 'json', token: process.env.GITHUB_TOKEN, handlers: [ (options, next) => { // Authorization if (options.token && !options.headers.authorization) { options.headers.authorization = `token ${options.token}`; } return next(options); } ] }); module.exports = instance; ``` ### Errors We should name our errors, just to know if the error is from the API response. Superb errors, here we come! ```js ... handlers: [ (options, next) => { // Authorization if (options.token && !options.headers.authorization) { options.headers.authorization = `token ${options.token}`; } // Don't touch streams if (options.isStream) { return next(options); } // Magic begins return (async () => { try { const response = await next(options); return response; } catch (error) { const {response} = error; // Nicer errors if (response && response.body) { error.name = 'GitHubError'; error.message = `${response.body.message} (${response.statusCode} status code)`; } throw error; } })(); } ] ... ``` ### Rate limiting Umm... `response.headers['x-ratelimit-remaining']` doesn't look good. What about `response.rateLimit.limit` instead?
Yeah, definitely. Since `response.headers` is an object, we can easily parse these: ```js const getRateLimit = (headers) => ({ limit: parseInt(headers['x-ratelimit-limit'], 10), remaining: parseInt(headers['x-ratelimit-remaining'], 10), reset: new Date(parseInt(headers['x-ratelimit-reset'], 10) * 1000) }); getRateLimit({ 'x-ratelimit-limit': '60', 'x-ratelimit-remaining': '55', 'x-ratelimit-reset': '1562852139' }); // => { // limit: 60, // remaining: 55, // reset: 2019-07-11T13:35:39.000Z // } ``` Let's integrate it: ```js const getRateLimit = (headers) => ({ limit: parseInt(headers['x-ratelimit-limit'], 10), remaining: parseInt(headers['x-ratelimit-remaining'], 10), reset: new Date(parseInt(headers['x-ratelimit-reset'], 10) * 1000) }); ... handlers: [ (options, next) => { // Authorization if (options.token && !options.headers.authorization) { options.headers.authorization = `token ${options.token}`; } // Don't touch streams if (options.isStream) { return next(options); } // Magic begins return (async () => { try { const response = await next(options); // Rate limit for the Response object response.rateLimit = getRateLimit(response.headers); return response; } catch (error) { const {response} = error; // Nicer errors if (response && response.body) { error.name = 'GitHubError'; error.message = `${response.body.message} (${response.statusCode} status code)`; } // Rate limit for errors if (response) { error.rateLimit = getRateLimit(response.headers); } throw error; } })(); } ] ... ``` ### The frosting on the cake: `User-Agent` header. ```js const package = require('./package'); const instance = got.extend({ ... headers: { accept: 'application/vnd.github.v3+json', 'user-agent': `${package.name}/${package.version}` } ... }); ``` ## Woah. Is that it? Yup. View the full source code [here](examples/gh-got.js). Here's an example of how to use it: ```js const ghGot = require('gh-got'); (async () => { const response = await ghGot('users/sindresorhus'); const creationDate = new Date(response.created_at); console.log(`Sindre's GitHub profile was created on ${creationDate.toGMTString()}`); // => Sindre's GitHub profile was created on Sun, 20 Dec 2009 22:57:02 GMT })(); ``` Did you know you can mix many instances into a bigger, more powerful one? Check out the [Advanced Creation](advanced-creation.md) guide. got-11.8.5/documentation/migration-guides.md000066400000000000000000000132431424347352000210500ustar00rootroot00000000000000# Migration guides > :star: Switching from other HTTP request libraries to Got :star: ### Migrating from Request You may think it's too hard to switch, but it's really not. 🦄 Let's take the very first example from Request's readme: ```js const request = require('request'); request('https://google.com', (error, response, body) => { console.log('error:', error); console.log('statusCode:', response && response.statusCode); console.log('body:', body); }); ``` With Got, it is: ```js const got = require('got'); (async () => { try { const response = await got('https://google.com'); console.log('statusCode:', response.statusCode); console.log('body:', response.body); } catch (error) { console.log('error:', error); } })(); ``` Looks better now, huh? 😎 #### Common options Both Request and Got accept [`http.request` options](https://nodejs.org/api/http.html#http_http_request_options_callback). These Got options are the same as with Request: - [`url`](https://github.com/sindresorhus/got#url) (+ we accept [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) instances too!) - [`body`](https://github.com/sindresorhus/got#body) - [`followRedirect`](https://github.com/sindresorhus/got#followRedirect) - [`encoding`](https://github.com/sindresorhus/got#encoding) - [`maxRedirects`](https://github.com/sindresorhus/got#maxredirects) So if you're familiar with them, you're good to go. Oh, and one more thing... There's no `time` option. Assume [it's always true](https://github.com/sindresorhus/got#timings). #### Renamed options Readability is very important to us, so we have different names for these options: - `qs` → [`searchParams`](https://github.com/sindresorhus/got#searchParams) - `strictSSL` → [`rejectUnauthorized`](https://github.com/sindresorhus/got#rejectUnauthorized) - `gzip` → [`decompress`](https://github.com/sindresorhus/got#decompress) - `jar` → [`cookieJar`](https://github.com/sindresorhus/got#cookiejar) (accepts [`tough-cookie`](https://github.com/salesforce/tough-cookie) jar) It's more clear, isn't it? #### Changes in behavior The [`timeout` option](https://github.com/sindresorhus/got#timeout) has some extra features. You can [set timeouts on particular events](../readme.md#timeout)! The [`searchParams` option](https://github.com/sindresorhus/got#searchParams) is always serialized using [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) unless it's a `string`. To use streams, just call `got.stream(url, options)` or `got(url, {isStream: true, ...}`). #### Breaking changes - The `json` option is not a `boolean`, it's an `Object`. It will be stringified and used as a body. - The `form` option is an `Object`. It can be a plain object or a [`form-data` instance](https://github.com/sindresorhus/got/#form-data). - Got will lowercase all custom headers, even if they are specified to not be. - No `oauth`/`hawk`/`aws`/`httpSignature` option. To sign requests, you need to create a [custom instance](advanced-creation.md#signing-requests). - No `agentClass`/`agentOptions`/`pool` option. - No `forever` option. You need to use [forever-agent](https://github.com/request/forever-agent). - No `proxy` option. You need to [pass a custom agent](../readme.md#proxies). - No `auth` option. You need to use `username` / `password` instead. - No `baseUrl` option. Instead, there is `prefixUrl` which appends a trailing slash if not present. It will be always prepended unless `url` is an instance of URL. - No `removeRefererHeader` option. You can remove the referer header in a [`beforeRequest` hook](https://github.com/sindresorhus/got#hooksbeforeRequest): ```js const gotInstance = got.extend({ hooks: { beforeRequest: [ options => { delete options.headers.referer; } ] } }); gotInstance(url, options); ``` - No `jsonReviver`/`jsonReplacer` option, but you can use `parseJson`/`stringifyJson` for that: ```js const gotInstance = got.extend({ parseJson: text => JSON.parse(text, myJsonReviver), stringifyJson: object => JSON.stringify(object, myJsonReplacer) }); gotInstance(url, options); ``` Hooks are powerful, aren't they? [Read more](../readme.md#hooks) to see what else you achieve using hooks. #### More about streams Let's take a quick look at another example from Request's readme: ```js http.createServer((serverRequest, serverResponse) => { if (serverRequest.url === '/doodle.png') { serverRequest.pipe(request('https://example.com/doodle.png')).pipe(serverResponse); } }); ``` The cool feature here is that Request can proxy headers with the stream, but Got can do that too: ```js const stream = require('stream'); const {promisify} = require('util'); const got = require('got'); const pipeline = promisify(stream.pipeline); http.createServer(async (serverRequest, serverResponse) => { if (serverRequest.url === '/doodle.png') { // When someone makes a request to our server, we receive a body and some headers. // These are passed to Got. Got proxies downloaded data to our server response, // so you don't have to do `response.writeHead(statusCode, headers)` and `response.end(body)`. // It's done automatically. await pipeline( got.stream('https://example.com/doodle.png'), serverResponse ); } }); ``` Nothing has really changed. Just remember to use `got.stream(url, options)` or `got(url, {isStream: true, …})`. That's it! #### You're good to go! Well, you have already come this far :tada: Take a look at the [documentation](../readme.md#highlights). It's worth the time to read it. There are [some great tips](../readme.md#aborting-the-request). If something is unclear or doesn't work as it should, don't hesitate to [open an issue](https://github.com/sindresorhus/got/issues/new/choose). got-11.8.5/license000066400000000000000000000021251424347352000137500ustar00rootroot00000000000000MIT License Copyright (c) Sindre Sorhus (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. got-11.8.5/media/000077500000000000000000000000001424347352000134625ustar00rootroot00000000000000got-11.8.5/media/logo.ai000066400000000000000000013103731424347352000147450ustar00rootroot00000000000000%PDF-1.5 % 1 0 obj <>/OCGs[5 0 R 27 0 R 48 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream application/pdf got logo Font: Hero Light Sindre Sorhus Adobe Illustrator CC 2014 (Macintosh) 2015-05-08T21:45:33+02:00 2015-05-08T21:49:52+02:00 2015-05-08T21:49:52+02:00 256 256 JPEG /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A6/5888eZ7zzOn5f+QREv mJoVudZ1q4X1LfSrV/sOU6STydY0PsSKGoVQ9v8A846eQ7qlx5tlv/N2qt8Ut9ql5cEcj19OGJ44 0Xf4VoaDviqy5/ILTdJU3X5d63qPk/Uo/jiihuJbrT5GHQT2ly0iuvyO3h2xVnvlCTzXJ5ftT5rh tYNeUMl4ti7SW7FWKrInNVZeagNx3pirE/zF88+YI9csvInklI5POGqQm5mvZ15W2m2Ktxa6mWh5 MzfDGnc9ewZVBW3/ADjv5GuwLnzhLfecNXYVl1DVLqegY/aEUETxxxp/KtDQbVxVq5/5x98r6dW7 8iX195M1hPiiuLC4llt3cdBcWs7SRyp4rtiqN/L7zzq+q3+p+RfO9tHZ+cdMh5zfVmZbe/sZDwW8 tSCGUHo4rVW8Psqqpf8AQvf5d+Oq/wDcVv8A/qtirCfzC/KLyno2t+SrTT59Ujg1nWVsdQU6netz gNvLJxBaU8fiQbjFWbf9C9/l346r/wBxW/8A+q2Ksp8neQ9A8owXMOj/AFrhdsrzG6up7o1QEDiZ 3fj17YqyHFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq8t/wCceLf675Nu/Odw K6p5y1C71O6dt2WMTvDbw13+COOP4R2rir1F3REZ3YKiglmJoABuSScVYt/ytj8rP+py0P8A7iVn /wBVMVTvRfMOga7atd6JqVrqlojmJ7iynjuIw4AJQvEzLyAYGmKvO/yVhOpa359833Q5Xupa9cad byN1Wx0ukECDw35VAxV6nirsVeWfnNANL8weQ/OVqON7p+uW+l3TrtzsNUrBMrU+1xbiVB74q9Tx V5r+bn/KTflt/wCBGn/UJPir0rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq8s/5x4u1svKN55IuTx1fyZf3WnXcbbM0LzPNbTgfySRv8J70xV6kyqylWAZWFGU7gg9jiqQf 8q88gf8AUs6V/wBINt/zRirDvyksLGw88/mXaWNvFaWkWq2git4EWONQbGMkKigKNz2xVZ+T840X zb578j3ZKXdvq02uacrE/vNP1PjIpjr1EclVYjufHFXqmKuxV5X+cE41rzb5E8j2hL3dxq0Ouaiq k/u9P0zlIxkp0EklFUnuPHFXqmKvNfzc/wCUm/Lb/wACNP8AqEnxV6VirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVeb+f/y819/MMPnryHcxWPnC3jEF7a3HIWeqWy9ILkKd mX9h+vQVFAQqhLb8947Clr5z8qa55d1JPhl42cl9ZswG/oXNsHEg2/lxVbc/m95m8wo1l+XvlHUb u6lqq6zrMLadpkNf92FpP3s3HqURQcVZR+XHkWXyppt4+oXzar5h1m4N/rmqOAoluGULxjQfYijV QqL4fdiqB/Mj8ur7XbvT/Mvlm+XSPO2iBhpt84LQTwtu9pdqN2hf71JqMVSm2/O2XR1Fr+YPlnVP Lt/HtLeQW0moabJTq8NxaiU0p8RUrVR4nFWrn87pdXU2v5f+WdU8xX8nww3k9tJYabHXo81xciNq U+IKFqw8MVTb8t/y7v8AQrvUPMvma+XV/O2t0Go3yAiCCFTVLS0VgCsKbe7Hc4qzzFXn/wCaGl6l e+Yfy/ls7aS4is9fWa7eNSwiiFpOC70+yvucVegYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYqk7eZbZfMS6L6TeoRvNXYMU5gU+WC3XHt GI1HgVv3/C04wuxdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVYJN/5Mof6yf9Q4yPV5af8Axp/L/cM7yT1LsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVcSACSaAdTiqQ6h578p2DFJtRjeQbcIazGvh+7DAfTl0dPM9GJkEpb82/KgYgC5 YfzCMU/Fhln5OfkjjCvbfml5PmID3EluT/v2J/8AjQPgOkmE8YZDp+saVqKcrC7iuQOojcMR81G4 +nKJQlHmEgozIpdirsVdirsVdiqhe39jYwma8uI7eIftysEHy3wxiTyW1PTNV0/VLb61YTCeDkU9 RQQOS9RuBhlAxNFANsPnB/5WUPdkp/0jjK+ry8/+NP5f7hml7fWdjbNc3kywW6EBpXNFBYhRU/M5 OMSTQeptfb3NvcxLNbypNE32ZI2DKfkRUYkEc1VMCuxVQvr+ysLZrm9nS3gT7UkhAFT0HzwxiSaC 2lP+OvKP/V0h+8/0y38vPuY8Qd/jryj/ANXSH7z/AEx/Lz7l4g7/AB15R/6ukP3n+mP5efcvEHf4 68o/9XSH7z/TH8vPuXiDv8deUf8Aq6Q/ef6Y/l59y8Qd/jryj/1dIfvP9Mfy8+5eIO/x15R/6ukP 3n+mP5efcvEHf468o/8AV0h+8/0x/Lz7l4grWnm7yzeXCW1tqMMk8h4xx8qFj4CtN8BwzAshPEE3 ypLsVdiqSeaPN2l+XrUSXJ9S5kH7i0Qjm/uf5V98txYTM7IMqecj/HPnyYkH6vpfKnUx24p27mRv v+jM793h97XvJkumflDoMCqb+eW8l/aCn0o/uWrf8NlEtZI8tmQgE6X8u/Jiig0xKdN3lJ+8tlX5 nJ3p4QoXX5ZeTp1IWzaBv54pZAfuYsv4YRqpjqvAGM6n+UV3bN9Z0LUCZENY45jwkFP5ZUoK/wCx GXx1gO0gxMO5R0f8w/MGhXo0zzPBJIi7GRhSdAf2q9JF/wA65KemjMXBRIjm9Psr20vrWO6tJVmt 5RyjkXoRmvlEg0WxWwK7FXYqwTzh+Y4srhtK0NBdaly9N5AOao524qo+2/4D3zMw6axcuTCUu5Kd P/LbX9bmF/5lvnjZ9/SqHmoe38kfyFfllktTGG0AgQJ5s/8AL/l7T9BsTZ2PP0mcyMZG5EsQAT27 KOmYeTIZmyzApjN1/wCTIh/2P/JnKerzOX/jSH4/hZL5j8v2mvaa1hdSSRxlg4aIgHktaVqCCN8u x5DA2HpiLec3nkrzh5Vla/0G6e5t13dYhR6f5cJ5K4+VfkMzY54ZNpBr4SOTJvJv5j2etMljfqtr qZ2Wn93Kf8iv2W/yT9GUZtMY7jcMoytmeYrNAa3omn61Ymyv0LwFg44kqQy9CCMnDIYmwgi2Pf8A KqPKP++5/wDkaf6Zf+bmx4A7/lVHlH/fc/8AyNP9Mfzc14A7/lVHlH/fc/8AyNP9Mfzc14Apz/lX 5SSCR1jm5KpI/enqB8sRq5rwBhX5ceVtJ1+e+TUFdlt1jMfBuO7Fq1+7MvU5ZQqmERbOf+VUeUf9 9z/8jT/TMT83NnwB3/KqPKP++5/+Rp/pj+bmvAHf8qo8o/77n/5Gn+mP5ua8AV7D8tfK1jewXkMU pmt3EkXOQkB1NVNNuh3yMtVMikiAZTmOydiqVeZ/MNroOky30/xOPgt4a0Mkh+yv8T7ZZixmcqQT Tzjyl5XvfN2pS69rjM1kXNBUj1WB+wvhGvTb5DvmdmyjGOGPNriL3L1mGGGCJIYUWKKMBUjQBVUD oAB0zXE22r8CuxV2KuxVKvMflrTdesGtbxAHAJguABzjbxU+HiO+WYspgbCCLS7yL5UvfLlndW9z dLcCaQPGicuKgChPxd27/LLNRmEyCAiIpk2Y7J2KsF/MnzfNp8SaNprH9JXYHqOn2o42NAFp+2/b wH0Zl6bDxeo8gwnJF+Q/I0Gh2y3l4ok1aZaux3EIP7C+/wDMcjqM5maHJMY0y/MZk7FWD3X/AJMi H/Y/8mcj1eYy/wDGkPx/CzjJPTuxVgnn3yBHfxvq2kp6Wpx/vJYk2E1N6inST375mafUV6ZcmEoq v5c+dH1i3bTdQb/cnbLVXbYyxjap/wApe/3+OR1ODhNjksZWzbMVm7FXYq7FVK6/3lm/1G/UcI5q 8x/Jf/erVf8AUh/W+Z+t5BrxvU817Y7FXYq7FXYq7FXknmuefzb55h0S2ciztHMJYdBx3nk+YpxH y982WIDHj4jzLUdzT1Wzs7eytIrS2QRwQKEjQdgBTNdKRJstqtgV2KuxV2KuxV2KuxV2KqF9eQ2V lPeTnjDbxtJIfZRU4Yxs0peY/l5YzeYfNF75kvxyED841O49Z/sAe0aDb6M2GplwQEA1xFm3qua5 sdirsVYPdf8AkyIf9j/yZyPV5jL/AMaQ/H8LOMk9O7FXYq8m8/6bP5b8zWvmHTR6cdw/qMB9kTD7 an2kU/rzY6eXHAxLVIUbeo6ffQX9jb3sBrDcRrInjRhWh9xmBKNGi2hEZFXYq7FVK6/3lm/1G/Uc I5q8x/Jf/erVf9SH9b5n63kGvG9TzXtjsVdirsVdiqD1q/Gn6ReXx620LyKD3ZVJUfSclCPFIBBL z/8AJ3Ti7ajq8vxSMwt43PWp/eSffVczdbLlFhAPTMwGx2KuxV2KuxV2KuxV2KuxVh35rX5tfKjw qaNeTRw7deIrIf8AiFMytJG5+5hM7In8tdOWy8o2hpSS6LXEh8eZov8AwirkdVK5lMBsyjMdk7FX Yqwe6/8AJkQ/7H/kzkerzGX/AI0h+P4WcZJ6d2KuxVjn5haYuoeU75aVktl+sxnwMXxN/wAJyGX6 aVTDGQ2Sz8pNRa58tPauatZTMi/6j0cf8MWyzWRqd96IHZm2YjN2KuxVSuv95Zv9Rv1HCOavL/yY cC91NO7RRNX5Mw/jmfreQa8b1XNe2OxV2KuxV2Ksa/MeRo/JepMuxIiX6GmRT+By/Tf3gYy5IX8q olTyhCw6yyyu3zDcf1LktWfWsOTL8xmTsVdiqX67rlnommyaheB2hjKqVjAZiWNAACVH3nJ48Zma CCaYr/yuLyz/AMs17/wEX/VXMj8lPvDHjDv+VxeWf+Wa9/4CL/qrj+Sn3heMO/5XF5Z/5Zr3/gIv +quP5KfeF4wmGg/mPoOtalHp1tFcRXEoYx+siBTwUsRVXfsDkMmmlEWUiQLKsx2Tzj853YWWlp+y 0krH5hVA/XmdouZa8jN/LiLH5f0xF+ytpAB9Ea5iZPqPvZjko+Z4NXl0s/ouVo7mNg5CGjOgBqqn x75WXA7Shmli/dGpD7QgPKnmtdQUWV6QmoIKAnYSAdduzDuMQXF7L7U8b0T2yD7f2slwu6YPdf8A kyIf9j/yZyPV5jL/AMaQ/H8LOMk9OkXmfzRBpEPpRUlv5BWOM9FH8z0/Ad8BLqu0u046cUN5np+k ofyWddmgmvNSmZ4Z6G3STr3qwH7KntiGrsc55RM8pJEuV/jkn2oRLLYXMTfZkidT8ipGSiaId0Xn H5LS/Fq8RJ3EDAdtvUB/hmdrhya8b07MBsdirsVUrr/eWb/Ub9Rwjmry38mf+OnqP/GBP+J5sNby DVjer5rm12KuxV2KuxVjv5hwNN5N1NF6hEf6I5Vc/guX6Y1kDGXJL/ynuVl8pLGOtvPLGfpo/wDx vk9WKmiHJmWYrN2KuxVTubW2uoWguYkngf7cUih0NDXdWqMIJHJUB/hbyz/1aLL/AKR4v+acn4s+ 8/NFB3+FvLP/AFaLL/pHi/5px8WfefmtB3+FvLP/AFaLL/pHi/5px8WfefmtB5/bWtra/nKsFrCk ECV4RRKEQVsamiqABUmuZhJOCz+N2H8T1PNe2PPfzltmbSdPuQNop2jJ/wCMiV/40zN0R9RDXkZZ 5SulufLGlzA1rbRKx/ykUI34rmNmFTPvZx5JtlaWK+a/KjXLHUtNBS/Q83RNi5G/JadHH45Eh0Pa nZfH+9xbTH2/tVfKnmtdQUWV6QmoIKAnYSAdduzDuMILZ2X2p43ontkH2/tSy6/8mRD/ALH/AJM4 OrhZf+NIfj+FOvM/mmDSYTFCRJfuPgj6hAf2n/gO+El2XaXacdPGhvkP2eZSHyx5Yn1Kf9L6vV0c 84436yn+Zv8AJ8B3+WAB1XZvZss0vGzbg779f2fjkzwAAUGwHQZJ6pBa5dC00W/uSaCG3lf6QhIy eMXIBBYH+S9uy22q3B+zI8MYPugcn/iYzL1p3AYY3pOYLY7FXYqpXX+8s3+o36jhHNXlv5M/8dPU f+MCf8TzYa3kGrG9XzXNrsVdirsVdiqhf2cd7Y3FnL/d3EbxP8nUqf14Yyo2peZflXqD6Zrt/oF2 eEkpPBT/AL+gJDqPmu/0Zn6uPFESDXA709UzXtjsVdirsVdirsVdirzH/wArZ/n/AMsGZ/8AyH/H e1/xPTswGxj/AJ80ltT8rXsEa8po1E8I78ojyIHuVqMu08+GYYyGyQ/lDrK3Gjz6W7fvbJy8a/8A FUprt8nrX55drIVK+9ECz7MNm7FWK+a/KjXLHUtNBS/Q83RNi5G/JadHH45Eh0PanZfH+9xbTH2/ tYY+vah+lk1J+P12NeNSNuSpw5EeOC3nDrsnjDKfrH6qZH5W8qy3sv6W1cGRZDziik3MhO/N69vA d/l1IDuOzOzDkPjZt73APXzLOgABQbAdBknqXYqwr819ZWz8uixU/v8AUHCAdxHGQzn7+I+nMrSQ uV9zCZ2R35b6S2neVLXmOMt2TdSD/jJTh/wgXI6mfFM+SYDZk+Y7J2KuxVSuv95Zv9Rv1HCOavLf yZ/46eo/8YE/4nmw1vINWN6vmubXYq7FXYq7FXYq8t/M3Q7rTNWg8z6dVKunrsv7EyfYc+zAUPv8 82GlyCUeAtcx1Z35W8yWmv6Wl5CQswotzBXeOTuPkexzEy4jA0zBtN8qS7FXYq7FXYq7FXltvcQX P5zmWBxJGGZOa7iqWRRh9DKRmwIrBv8Ajdr/AInqWa9sdirx7Wba68jec01C1QnTbglo0GwaJj+8 h+an7P0Zs4EZcdHm1HYvWdPv7XULKG8tJBJbzqHjceB7HwI7jNdKJBotoKIyKuxVJ7ryppFzqqaj JH8a7yRD7Dt2Zh/nXBTrsvZeGeUZCN+7ofenGF2LsVUrq6t7S2lublxFBCpeSRugUYQCTQV5FAtz 5986+q6sul29OQP7Fuh2X/WkP+e2bI1hx+bV9RewqqqoVQFVRQAbAAZrG1vFXYq7FVK6/wB5Zv8A Ub9Rwjmry38mf+OnqP8AxgT/AInmw1vINWN6vmubXYq7FXYq7FXYqpXlpbXlrLa3MYlt5lKSRt0I OGJINhXkWq6PrvkLWRqOms0umyGiuwJUqT/dTAd/A/SM2UJxzRo82ogxeg+WPPGi69GqxyC3vqfH ZyEBq/5B2Dj5fSMw8uCUPczErZDlDJ2KuxVZPPDBC808ixQxjk8jkKqgdyTsMIFq8382/mPJesdH 8sh5ZZz6bXaA8mr+zCOv+y+7xzOw6avVNrlLuTvyF5GTQoPrl5STVZ1o3QiJT+wp8f5j/madRn49 hyTGNMwzGZuxVLfMGg2OuaZJY3a7NvFKPtRuOjr8vxyzHkMDYQRby/TdU1/yBqzWN/EZtMmYsVX7 Ljp6sLHo3ip+nscz5QjmjY5tYJi9U0fW9L1i1Fzp86zR/tAbMh8HU7qc188ZiaLYDaOyCXYq7FUJ qmr6bpVo11fzrBCvQsd2Pgqjdj7DJQgZGggmnlWta9rnnvU00rSYmi05DyKtsCAf72dhWgHYfrOb GGOOEXLm1kkvSvLPluy0DTFs7b4nPxXE5+1I9Nz8vAZgZcpmbLYBSbZWl2KuxV2KqV1/vLN/qN+o 4RzV5b+TP/HT1H/jAn/E82Gt5BqxvV81za7FXYq7FXYq7FXYqsmghnieGeNZYpBxeNwGVgexB64Q aV595g/KO2mdrnQ5/qsleX1aUkx1/wAhxVl+muZmPWEbSazDuShL781fLw9OWKa6t16c1F0lO3xp ycD5tlvDhn+KR6grL+bXmNBxn0qIyVoaCVN/kS2D8nHvXjLR/MPz7qH7vTtMCctg0UEsjD6WJX8M fy2OPMrxFqPyP578xSrLr14beEGvGVg5HukMZ4D6aY+Pjh9IXhJ5s78t+TtE0CP/AESLncsKSXct GkPsD0UewzDy5pT5tgjSeZUl2KuxV2KoTVNJ07VbRrS/gWeBv2W6g+KkbqfcZKEzE2EEW861L8sN b0y6N75ZvWJXdYi/pTAfyhxRWHzpmdHVRkKmGBgeimnnv8wNH/d6tppnVesskTIT22kj/dn7sfy+ OX0leIhV/wCV0vwp+iBzp1+sbV+Xp/xwfkfNfEUn/MDz5q37vSNM9JW6SxxPKRX/AC2/dj6Rh/L4 4/UV4iV1h+WvmLWLkXnma9dK9Y+Ylmp/KDuiD5V+WMtVGIqAUQJ5vRdI0XTNItBa6fAsMQ3YjdmP 8zMdycwpzMjZZgUjcgl2KuxV2KuxVZOheCRF+0ykD5kYQrzn8qNC1jT77UZb6zltUKLGplUpyYMS eNftD3G2ZuryRkBRa4B6TmC2OxV2KuxV/9k= proof:pdf uuid:65E6390686CF11DBA6E2D887CEACB407 xmp.did:9fa28b47-4c62-4038-9043-956c78b3ed20 uuid:d1e42c04-3cdb-ba4d-a392-757ab43c8ee9 xmp.iid:8371700e-157a-4940-bf59-8401dc1338a2 xmp.did:8371700e-157a-4940-bf59-8401dc1338a2 uuid:65E6390686CF11DBA6E2D887CEACB407 proof:pdf saved xmp.iid:16bdb7a9-88d3-420f-acce-43f9589108f5 2015-05-08T21:21:15+02:00 Adobe Illustrator CC 2014 (Macintosh) / saved xmp.iid:9fa28b47-4c62-4038-9043-956c78b3ed20 2015-05-08T21:45:33+02:00 Adobe Illustrator CC 2014 (Macintosh) / Web Document 1 False False 500.000000 230.000000 Pixels Cyan Magenta Yellow Default Swatch Group 0 White RGB PROCESS 255 255 255 Black RGB PROCESS 0 0 0 RGB Red RGB PROCESS 255 0 0 RGB Yellow RGB PROCESS 255 255 0 RGB Green RGB PROCESS 0 255 0 RGB Cyan RGB PROCESS 0 255 255 RGB Blue RGB PROCESS 0 0 255 RGB Magenta RGB PROCESS 255 0 255 R=193 G=39 B=45 RGB PROCESS 193 39 45 R=237 G=28 B=36 RGB PROCESS 237 28 36 R=241 G=90 B=36 RGB PROCESS 241 90 36 R=247 G=147 B=30 RGB PROCESS 247 147 30 R=251 G=176 B=59 RGB PROCESS 251 176 59 R=252 G=238 B=33 RGB PROCESS 252 238 33 R=217 G=224 B=33 RGB PROCESS 217 224 33 R=140 G=198 B=63 RGB PROCESS 140 198 63 R=57 G=181 B=74 RGB PROCESS 57 181 74 R=0 G=146 B=69 RGB PROCESS 0 146 69 R=0 G=104 B=55 RGB PROCESS 0 104 55 R=34 G=181 B=115 RGB PROCESS 34 181 115 R=0 G=169 B=157 RGB PROCESS 0 169 157 R=41 G=171 B=226 RGB PROCESS 41 171 226 R=0 G=113 B=188 RGB PROCESS 0 113 188 R=46 G=49 B=146 RGB PROCESS 46 49 146 R=27 G=20 B=100 RGB PROCESS 27 20 100 R=102 G=45 B=145 RGB PROCESS 102 45 145 R=147 G=39 B=143 RGB PROCESS 147 39 143 R=158 G=0 B=93 RGB PROCESS 158 0 93 R=212 G=20 B=90 RGB PROCESS 212 20 90 R=237 G=30 B=121 RGB PROCESS 237 30 121 R=199 G=178 B=153 RGB PROCESS 199 178 153 R=153 G=134 B=117 RGB PROCESS 153 134 117 R=115 G=99 B=87 RGB PROCESS 115 99 87 R=83 G=71 B=65 RGB PROCESS 83 71 65 R=198 G=156 B=109 RGB PROCESS 198 156 109 R=166 G=124 B=82 RGB PROCESS 166 124 82 R=140 G=98 B=57 RGB PROCESS 140 98 57 R=117 G=76 B=36 RGB PROCESS 117 76 36 R=96 G=56 B=19 RGB PROCESS 96 56 19 R=66 G=33 B=11 RGB PROCESS 66 33 11 Grays 1 R=0 G=0 B=0 RGB PROCESS 0 0 0 R=26 G=26 B=26 RGB PROCESS 26 26 26 R=51 G=51 B=51 RGB PROCESS 51 51 51 R=77 G=77 B=77 RGB PROCESS 77 77 77 R=102 G=102 B=102 RGB PROCESS 102 102 102 R=128 G=128 B=128 RGB PROCESS 128 128 128 R=153 G=153 B=153 RGB PROCESS 153 153 153 R=179 G=179 B=179 RGB PROCESS 179 179 179 R=204 G=204 B=204 RGB PROCESS 204 204 204 R=230 G=230 B=230 RGB PROCESS 230 230 230 R=242 G=242 B=242 RGB PROCESS 242 242 242 Web Color Group 1 R=63 G=169 B=245 RGB PROCESS 63 169 245 R=122 G=201 B=67 RGB PROCESS 122 201 67 R=255 G=147 B=30 RGB PROCESS 255 147 30 R=255 G=29 B=37 RGB PROCESS 255 29 37 R=255 G=123 B=172 RGB PROCESS 255 123 172 R=189 G=204 B=212 RGB PROCESS 189 204 212 Adobe PDF library 10.01 True endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/ExtGState<>/Properties<>>>/Thumb 54 0 R/TrimBox[0.0 0.0 500.0 230.0]/Type/Page>> endobj 50 0 obj <>stream HlA7 Eu ]8$R+#0@Ue hHI'5S9Yi_l_9S[M'o?n~l{IMʴn߷XUJc /Ɇ9X%LHqȣ],_։ӞuƎ]f#{ͧETu=E=ԆY&kj ݋\Ӯ&j΁*,OǪcGKR)LjO ^Go˖M++dɔjVHՂP2L Dҝ,*YP54Js*i6IC;@ۃ iC!ȅ4HZ?#Qrš6FL1*aѮN8]6WPhx An 3X'X|IȕG,H'dUzJ艠(pPi"+p 2hoXbݭE&v^ Ymu'(tP k/0hM_X&qYgŧ}[WNcY dGޱz9>|NmWKw9Y}hpc\uer >stream 8;Yi\b7VZ8#_l!I$u]H78Js@!40+/JPis8H41f)u*;nat+%b^@>C>NAYIsBeCe(': bna*C9hr]N^3/h4%J%`/b]ZuIk-_3]I5QtnKu%mJetb+-Sl1]\@Y&CIE;$?;3Q-W] V#1e/=^]OqB"PaF-C=]!\J]5$UtP"NhPt0"TWf"tji3`bO_$1=%hP,Qho<=88OSs1 aR?Ah-aps3N!KGn+Wp#Vess;kZSSWJbV`H6/":*To4>]0=fZ$Y4.eD-S.FKPSEi8f "+"ssh6EmT^"m7hSZI;Ko,"s^rk%kA!+Lm;"o~> endstream endobj 55 0 obj [/Indexed/DeviceRGB 255 56 0 R] endobj 56 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 48 0 obj <> endobj 57 0 obj [/View/Design] endobj 58 0 obj <>>> endobj 53 0 obj <> endobj 52 0 obj [/ICCBased 59 0 R] endobj 59 0 obj <>stream HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= x-[0}y)7ta>jT7@tܛ`q2ʀ&6ZLĄ?_yxg)˔zçLU*uSkSeO4?׸c. R ߁-25 S>ӣVd`rn~Y&+`;A4 A9=-tl`;~p Gp| [`L`< "A YA+Cb(R,*T2B- ꇆnQt}MA0alSx k&^>0|>_',G!"F$H:R!zFQd?r 9\A&G rQ hE]a4zBgE#H *B=0HIpp0MxJ$D1D, VĭKĻYdE"EI2EBGt4MzNr!YK ?%_&#(0J:EAiQ(()ӔWT6U@P+!~mD eԴ!hӦh/']B/ҏӿ?a0nhF!X8܌kc&5S6lIa2cKMA!E#ƒdV(kel }}Cq9 N')].uJr  wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 51 0 obj <> endobj 60 0 obj <> endobj 61 0 obj <>stream %!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 17.0 %%AI8_CreatorVersion: 18.0.0 %%For: (Sindre Sorhus) () %%Title: (logo.ai) %%CreationDate: 08/05/15 21:49 %%Canvassize: 16383 %%BoundingBox: 88 -320 528 121 %%HiResBoundingBox: 88.5918405504581 -319.78815515496 527.273245834404 120.785820007324 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 13.0 %AI12_BuildNumber: 18 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 70 -355 570 -125 %AI3_TemplateBox: 320.5 -240.5 320.5 -240.5 %AI3_TileBox: -83 -519.5 700 39.5 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -179 133 1.0909 1448 862 18 0 0 -4 38 0 0 0 1 1 0 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:-80 -540 %AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 62 0 obj <>stream %%BoundingBox: 88 -320 528 121 %%HiResBoundingBox: 88.5918405504581 -319.78815515496 527.273245834404 120.785820007324 %AI7_Thumbnail: 128 128 8 %%BeginData: 6288 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD04FF7DFD052752A8FD08FF7DFD0527527DFFFFFFA85227522752 %275227522752275252FD54FFA82727A8A8FFA8A852F852FD06FF2727A8FD %04FF7D2727FFFFFFFD06A82752FD04A87DA8FD53FFA8277DFD07FFA852A8 %FD04FF2752FD08FF2727FD08FF527DFD59FF2752FD0EFF5227FD0AFF2752 %FD07FF277DFD58FF7D27FD0FFFF8FD0BFFA827FD07FF527DFD58FF277DFD %0EFF5227FD0CFF277DFD06FF277DFD58FF27FD0FFF5252FD0CFF7D52FD06 %FF527DFD58FFF8FD07FFFD0552A8FFFF277DFD0CFF7D52FD06FF277DFD58 %FF27FD06FFA87D527D7D5227FFFF527DFD0CFF7D52FD06FF527DFD58FFF8 %FD0BFFA852FFFF5252FD0CFF5252FD06FF527DFD58FF527DFD0AFFA827FF %FFA827FD0CFF277DFD06FF527DFD58FF7D27FD0AFFA827FFFFFF277DFD0A %FF7DF8FD07FF277DFD59FF2752FD09FF7D27FFFFFF7D27A8FD09FFF8A8FD %07FF527DFD5AFFF852A8FD06FF7DF8A8FD04FF5227A8FD06FFA8F87DFD08 %FF277DFD5BFF272752A8A8A8522727FD07FF7D2752A8A8FF7D52F87DFD09 %FF527DFD5CFF7D52272727527DFD09FFA87D2727275252FD0BFF7DA8FDFC %FFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFC %FFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFC %FFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFC %FFFDFCFFFD1DFF8A8AFD7DFFA85F3BFD7EFF5F66AFFD7CFFAF663BAFFD7D %FF3B66AFFD7CFFAF5F3BFD7EFF3B66AFFD7DFF5F3BFD50FFA9FFA8A9A8AF %A9FD27FF3B65AEFD4CFFA884537E535A5353537E537E7EA9A8FD1BFFA8A8 %53A9FFFFAE663BAFFFFFA97E84FD0DFFA87E7D7E7E7E7D7E7E7E7D7E7E7E %7DFD077E7D7E7E7E7D7E7E7E7D7E7E7E7D7E7EAFFD12FFA95A7E537E53FD %077E537E537E7EFD19FF7E7E535AA8FFFFFF5F66AFFFFFAF537E7EA9FD0C %FF537E537E537E537E537E537E537E537E537E537E537E537E537E537E53 %7E537E537E535AA8FD10FFA87E535453A8A8FD07FFA9A97E7E5353537EA8 %FD13FFA97E53545384A8FD04FF663BAFFFFFA9A8595A537EA8FD0AFFAFA8 %FFA8A9A8FFA8A9A8FFA8A9A8FFA8A9537EA8A9A8FFA8A9A8FFA8A9A8FFA8 %A9A8FFA8FD10FF7E7E537EA8FD0FFF7E7E537EA8FD11FFA87E537E84FD07 %FF5F66AFFD05FFA87E535A7EFD1AFF7E53FD20FF535453A8A8FD11FFA87E %535A7EFD0FFFA85A537EA8FD08FF603BFD08FFA8535353FD19FF537EA8FD %1EFF7E7E5AFD16FFA9537E7EFD0DFFA97E53A9FD0AFF5F66AFFD09FF7E7E %7EFD18FF7E53FD1EFF7E5353FD18FFA8537EFD0CFFA87E53A8FD0BFF663B %FD0BFF7D5A59FD17FF537EA8FD1CFF7E7E5AFD1BFFA9FD0CFF7E53A9FD0C %FF5F65AEFD0BFF7E7E7EFD15FFA97E5AFD1CFFA85453A9FD27FFA8537EAF %FD0BFFAF663BAFFD0CFF53547EFD15FF537EA8FD1BFF7E53A9FD28FF547E %A9FD0DFF5F66AFFD0CFFA9537EA9FD13FFA97E5AFD1BFFA8537EFD28FF7E %547EFD0EFF663BAFFD0DFF7E537EFD14FF537EA8FD1AFF537EA8FD27FFA9 %7E53FD0FFF5F66AFFD0DFFA87E53FD14FF7E53FD1AFF7E5359FD28FFA853 %7EFD0FFF603BFD0FFF7D547EFD13FF537EA8FD19FF7E53A9FD28FF7E7EA8 %FD0FFF5F66AFFD0FFF537EFD13FF7E53FD1AFF537EA8FD27FFA87E53FD10 %FF663BFD0FFFA97E53A9FD12FF537EA8FD18FFA87E7EFD28FFA9537EFD10 %FF5F65AEFD0FFF7E5A84FD11FFA97E53FD19FF7E53A8FD28FF7E537EFD0F %FFAF663BAFFD0FFFA8537EFD12FF537EA8FD18FF7E7EA8FD28FFA853A9FD %06FF8AFD09FF5F66AFFD09FFAEFD05FFA87E7EFD11FFA97E54FD19FF7E53 %FD29FF7E54A8FD05FF843B5FFD08FF663BAFFD07FFAE3B3BAEFD04FFAF53 %7EFD12FF537EA8FD18FF5A7EA9FD11FFA8A9A8A9A8A9A8A9A8A9A8A9A8A9 %7EFD08FF7E53A9FD06FF5F3B5FFD07FF5F66AFFD06FFAF3B415FFD06FF7E %53FD12FF7E53FD18FFA87E53FD11FFA854FD0F53FD07FF595AA8FD07FF3B %3B5FFD06FF603BFD06FF8A3B3B5FFD07FF537EA8FD11FF535AA8FD18FF7E %7EA9FD11FFA8A9A8A9A8A9A8A9A8A9A8A9A8A9537EFD07FF7E53A9FD08FF %5F4160FD05FF5F66AFFD04FFAF3B6560FD08FF7E59FD12FF7E53FD18FFA9 %7E53FD20FF7E53FD07FF7E54A8FD09FF3B3B5FFD04FF663BFD04FFAE3B3B %5FFD09FF537EAFFD11FF535AA8FD18FF7E7EA8FD1FFF5A7EFD07FFA853A9 %FD0AFF3B4160FFFFFF5F65AEFFFFAE3B4160FD09FFA87E7EFD11FFA97E53 %FD19FF7E53A8FD1EFFA87E53FD07FF7E537DFD0BFF3B3B5FFFFF663BFFFF %8A3B3B5FFD0AFFA85384FD12FF537EA8FD18FFA87E7EFD1FFF7E7EFD08FF %537EFD0CFF5F4160FF5F66AFAF3B4160FD0BFF7E7EA8FD11FFA97E54FD19 %FFA9537EA9FD1DFFA97E53FD08FF7E53A9FD0CFF3B3B5F663B8A3B3B60FD %0BFFA87E53FD13FF537EA8FD19FF7E53A9FD1EFF537EFD08FF7E5A7EFD0D %FF5F413B663B4160FD0CFFA9537EFD13FF7E53FD1AFF7E5353FD1EFF7E53 %FD08FFA9535AA8FD0DFFFD043B5FFD0DFF53547EFD13FF535AA8FD1AFF53 %7EA8FD1DFF597EFD09FF7E53A9FD0EFF5F418AFD0DFFA87E7EFD14FF7E53 %FD1BFFA8537EA9FD1CFF7E53FD09FFA85453AFFD0DFFAF84FD0DFFA97E53 %A8FD14FF535AA8FD1BFF7E53A8FD1CFF7E7EFD0AFF7E5A7EFD1CFFA8537E %FD14FFA97E53FD1CFFA85453A8FD1AFF7E5A53FD0BFF53537EFD1AFFA853 %5AA8FD15FF537EA8FD1CFF7E7E53A9FD18FFA87E53A9FD0BFFA9537E7EFD %18FFA9537EA8FD15FFA97E54FD1EFF7E5A53A8FD16FFA85A53A8FD0DFFA8 %535A7EFD16FFA953537EFD17FF537EA8FD1EFF7E7E537EA9FD13FF7E7E53 %A8FD0FFFA9537E7EFD14FFA8537E7EFD18FF7E53FD20FF7E5A537E7EFD10 %FFA9595353A8FD11FFA8535453A8FD10FFA87E53547EFD19FF535AA8FD20 %FFA87E537E7EA9A9FD0BFFA87E535A7EFD15FF7E7E537EA8FD0DFF7E7E53 %7EA8FD1AFF7E53FD23FFA8535A537E7DA8A8A9A8FFA8A87E7E5354537E7E %FD17FF7E7E535A5384A8FFA9FFAFFFA8A97E7E535353A8A9FD1BFF535AA8 %FD24FF847E535A537E537E537E5354537E7EA9A9FD1AFFA97E7E535A537E %7E7E537E5354537E7EFD1DFFA97E53FD28FF847E597E537E537E7EA8A8FD %1FFFA8A87E7E537E535A537E7EA9A8FD1FFFA8A9FD11FFFF %%EndData endstream endobj 63 0 obj <>stream %AI12_CompressedDatax$&?d& C;\;fH-Z[TLQժ XV}~<2Ef p\;??>#w~v~{ݛ:ȧ߼|WIn~?+ W/޼(w?}O߼__/޽G7_9>{8|j1҄5۳zD>;/^qz#gC`A|WMcLcf:M&)huޏ]hǑ˛_o7og{j/pz_R{Lȫgqzn~cg_~8~>+t^;?N>;ק(𡔟'_Œ/Jo|ۿiīpؠ p˗~+1>(m:Lc@M2M{q_{>ՕowdG|~߽|& ,xwX7/~'2'`\1_e4a}i8|bmS]5/xWe/>_3C{1`3qS?'ES>I|{wL vw6)^7_a!ֺw׿RE R?ר7_~3\Ϳ?E?'ݐgۀ|ˁi%)8U>~|/gC6'%C&M4XU=Os=~_xmcٻ8߿ss+=? _kKv do6Yw]q~/>//=f~~޺lӛ/z'?{wOeMo?w?ӿ?{/}3`p>Że޼ӿ<^ޠt0|[`M? o}w _y'/܎p|a,H}vz;alLS쪸R|)A%KK&*7rB9rYnj lcWŭߔe(Iͫ2mʍ9<Կ..n]S'?nUYǦUKͪ Z^[MYOZemQyyjuU׫Q/W5owZ0?}Y*߮WןqwYhu\YqeS7lkn\Uu˚"%baF\s))ܱԓŗYUeLIɝ̍zEFll a99]t RN(w6">'Oo&|!)܄S8p7`@cq7 biJtNt}ba)O&%;;LnLB4ܸnMnnnN(ͭ$pJ|Pu:O;.3l?3p7|ٌK`p|.r{.to[<|{noo#0awozݠw]߷rYVfSZ/JZ)aUUl)[q=eSZ*72Jޔ*qUp7ŭ]*R?U9iYm׬v˂V]oLs32C7e 7<۲݀B@n+~t<%BpwL vy >;b;9<vv {@}w &-D5Cpqwn ʄ%k㺱tF[3.\Hy]pW*wء,oQ^h.7^ƎTvfhK+^xO(AÕI7Aev! w+R$a¦ Pio+rGKBANo]ī y6eysu/+gOvV?Y^4*ZK)aUDUR" W%F9CjerIqӚf@,% E7txdY-XeX)JqXNawR:V:o}:?;cQO/(?? )C 1/~݊Hî$3d @T42Y?*YrE$tt9(*" 9w"_|?CRYҐUiHKN"e(rEw"R,RQш‘Q ,e$JI OeSB%'ЈjVĥ % L(4QlJ>D E|+Iy$[d{#BZ,\rfY(?̔V5Zѡ F}Q9:ylJ!,Ã4eC~b; rky#}yЍMtA0c (0 +tǵlZ +R>qV6Cxji7~zQfd ]B,2;V}߿}bS;;c4`&c`8)DJmW_\o8mlUT^]8=@ѹ^`o5>?/|s+-J(TŠ\. ŋ|"L\G$LFy+]1*x#zKՖ5:T_')5/۽~kG(8d-n8;~EO*Z[gxrV>y|gx(]' @rO"i 嚀y0sfE ԳZ(VŞ][;1ԔSlqsl aPⰉ٨_MuXN]1W_xŠ6%C,hʳ9rQsJ31r.kvb^lZzSzo>֚? 5d *W~nXcϾCg%Թ[Y]fxYvi>4μ2W⼬^nxІO BO\ofczbU pU"kO/z͗| P58{p}µxy<z}|a^nF^AO@b3)Vao}ٯϭ^s#}GS6=-D()GTx.jLA\)Bs̢~Q̄#3>o2UdqHϮ-Gy^)h>Nͦ)!koի[/-]5sNKm=AU@R'} U fyd4kc zXEًz Vk.EV J8Ż8hK?"mi|$*WTrT ԶEC(0$ar)S)33{~T'{I\\4<2,0[l[!MrSr3e6>%BE6wQ%J>q唚mWCyZy"obݭuc*qj>A-PYLJ8}Mηv eoRuxnVq#.eėوD Y5\YTpX[At[1c'*/-2ӬߌAcZiOm}39#xU' +Wmqn'qe:GsmYSoVA]ÎٮZ:Ζ2Op`׼\,1M_k;_ǰ I"-΂4PEVY!+chab-/BZ=Nn"o;V [;s/0"Wΰ8J[㷳5G74:5: Sqhщަ '$*f$i5zt1oK$R6 %Imvt+j4 Sf5j(4P*Zջr]Dz.bAK% ^RB=tWJN)zشmE(Bs>5dĮ^^qPۈi(״b@/03޸2tݾOyRܔ=eDŽ1Cscīٙf >6ߕQȬh{>_^~壃ŰMfeeS(]Eyj:^7r'X>mwVxX߾i7(ʏ<1?,ʀ,lo{(n6]K@RݰL3a!I? xep>M.zYpvJcSҐꆽSKE*I*Ubr$oiBOlQ-T%-jV,{tKW6^CsT0CGd{4gx"zex=ʃ4O~~ȥ'<֧uJ0`6x,ҥrE(KX1J\JsB-yp w]h$%֫aNh̨_%UnEC!CFXZ!kYU5-"< "A-rZZK7[eZW?]~)=Fگ? OkvO-ӛ*. {6:b<_ `QO 3]&9&8ӰPľ6T#9)- j 6Vb&JxMaU o1~k(vjk1u--6amژZVkںb5Ƥ=kf3e=z%q[O-秔iͮhvE:,8I?Q(OGx>DA~ ?Qwx͇iOi1)i4{;I8W0L6킙𡄭Iޙ^fĶ=Kc1 d!0IxFQ͑,8t~?`1v߶Ҏot`,\u:owK}}ysuC6SX\/o}N.IUNVtlLMu+gEV.v}6k 0'FES[s .!RA dŜ%F9cI/xpƪ|0F<7pi?Gls^9[nd[}5^w^mQӫv_*ubɠ ᤫP;7%FT5݊DhO!kNJ9˭biC@tbo~[b] b\W DEpZc8Yqű8VKõ9oR ^5xr?mOwn@Qw yiԆZ0׈/_FNy=y>;#,Vur\xcz]N8nsz\-__Ə|aSz.NrN W2$}_X:*E2;x\kC+\X{k@ޗ&^6tn(j|mҴK>wyF}}l~BW&"Ԗ 4UGVMΝ j cx(y&Q=;tqw0(^bcdIJwŽ3y#4u>_Ϗ3l%m_qZAO؊xm M %>bpX)̱Jq{'tD[GV##q~մq]Vg6o`,F[=9EsYܻ:j0r. ~cA'7.a鋎^r5K]##+xbb@bjd/ۧF^#[1̮a%  ˒M` ¯@uFOn*m#Oh Vp5*eҋe72;5(K崑u#dMŘ0H$MOE{y*|꺳pw%J+0J:} P5h*d.K⌿2v6t])a!߷ C d߬)\4xrMiyM@+`uqFaX'|$Wq2n͟p[aړ'[O^5c`ϋ5v~,xmݔ$dIC䆒%$SYDK9&}3p5ϽUu|V;PUpg$؍ch.- 2c<,kCŭqcW9 ]­M+&CK4Ǎ׋d^~VC4_+}_:TOEUkRAhtI - `ְ77uk M};\<ϞV.j߬?8W!:|PT'rsT4즶Stpr9Ғ&$N2 1$N,1w%@m1}JAm{犿M""ScDNXi0N3KD |_.EE>@Ha@za0Z i*AjZLՠ *v%2 Q?*wfO8瑿#WG6ۍSV>+ Gfo+VW47JӰ5s#jZJUt?K2lsřYfQ]ӵW^wa?]I6WT?xm?~ȂC.:e[ tY,1 ?4Wm:";\)E:VJK2,RJ9D%! h@(f5_&iy0֊9h؛aʶE{oVW+>([ W* 0{2" #KY TUDY,3BZ EYe'9o, [BObJ(ƃzvy׽nlro/cE O* A_=cO?i*栔?V6(1ЅNٍ^jm jlB4Ǽ[%3:|vj#קj&|:s:XLOVFðg4 ]^~2sOXFXS$I{II&Y-N)sz%A^MK^7w3C=}[ѴˊPXrla0UU6aWksYFT{_'py-z0m&y7eG_SG#qe,cLj[I=-硽r5'xs|ױn>rk*̮5t5-CU_C_dۜ KLnW13xi`I`NK Ϸ Q:2HAJPU6w޲AU WvJŨ%Qn ?I”p[ґ?!G_4i'zߨ-͒T+7lM8تa1nURcic.V[dQ+駶G_=+TB۰Cvd]-~ zecҸ%qxb;F5-ՇI~ݔ7Q~^ҡ(rt)< t RZ"&o2Zb&jUMj,`~ ?m5POP2|?y߽xGu_i޽|34<->Fwidg hBAolm:R!Lphd-a_1Cw< t.#3I qE fa‹FY}%VgS@"Pil͑`f:BD6t7-*c@{ X/#'gTe1ȴ0(DKos}Å#dq,#^"-/ ث1Nq2؀u I#$& S$L,@G˂[IƮ† b `'^B?IM8TfÃdO|e,|({}} K'$"cobH""JDE&><6#.ǘ]T3wQMBĪ bb4!#0$>vSf,wnLOYzS)v6÷Q*]ιcpp#?a3)#VI(h*׾*1[:: G¤jz8x@t&}aGF,Ph$̵Db3T^%]Bc/Y3c-bIګ!jl \8!*8 V9٩+T-.Fw@x8MEY=J)[cp0_[}/3?ʴAtmÄzHn׃p*1rsBVw|ce<$Aoy=zLyv#x!aohB{k_hV;sL\/߿<W=A/ٛ? orõL{آ!TfEbѝ(xL(2f㴸˸$n ,L"C"zJЄ \7T[ʤd + 6vBoeW]!DW1oHwm%H2kdRq'CՎXZ[i;Fx#B޵] yoeNl3_cD9D/*m% dބSy]- u•HJbcx[ w?* rYrBʟ:3̑nsS.eUdSik3G>fT9i\h?qsWiC;+x#@E:PlXJŽKun|ƛy$ 8C(A]Z0Oׄm x{\F0TOEȓHfwaaJ@Cr`%@|R%RU#>Lo;yp"MfP"0=Xpn |9APr- 23A(LOř\'"Dӂ3 |=g>/ݡR҉Ѩ#3'7={&a1[f^qɷb!qVxT,@m gS?+ + ~ gZh+6,QYޚYռ9>s %ґ?l mm]㽃)Lp轑ChM SI8\Յ<%44.i+഻2>=ρo?Rݵ41?U58?!Yި&2qňLeLf9m#p\ Kdve~e K¶#}0UOe$|xA*4>:Q[# LTU$ ֎&&4yr($bx(5M0Ɉprwݗ#-F6<_fn.(`wy|nME&ڑFȄ/;b)q&L{30F>fL4Z$.H=%7}+.Xl Vnp4s. .N:іDkP'_C:QӍŪNT)/#*tNN."PtHݼBsEn>nlG؊ TV7SBH`dp,d kEڴqƑ-@#v∣ؔfAR !D^Kh׊309Pd.WjZQJ`t-vߩkNM7nv[y=Anu =U^d*NIq<4Q) 4֣VԻs9)߲zNWQFm;-:ϱ?3RONYϹ6wJ߿Y1F7lֵ, F8J\oY}68j @)7TەTEਟ ic c&N-pdd?ѭLc̍w2S{Yľ;(&68`d]@i U}q@3ŭOʨ4|nނ, xʀ1MO#iڸ&H-nqdq]$v0qTɣR;{#OR?WjJ'$q+M[1MFU7$#C tm!NLa\h{:ldneMq @G׾QKr>2k;AMx_}puB^C?1*!jT-tC"GӰ׏-8`Ǹ;EN/ik0C?* RFnvn~Uh:R?v wh[!q8 pB ځ4#ݲv "= 5Z ZipKɯE:T I6ɒ "K6z%R8Ѿ{"Dv"rH\h:Eja;[s%:F6zs'lzG=^iHe=sR@i" ƣq{wˮŮSN?P+:r$(82oBE6.OE6cN]=_ҝn*]u3{Gysߔ&7VlGg;kŮͰ\Qyxر{TK}uݠKŮKn=6?Ig8S#ՓiǾ|,8heGnd3u8GiVpqsc-TLav ma9)D3!dQ^ި#lBܑ_^DFK]9r]?tM c>̊ vшkHӱb̍1(To˓p;k'zV :>:fQJUo Q=`L(+K2|.s + _<=F|Ur&A,;@sW;C[4Oj[a*z%uXeп6S—}FҰ(<"( "¢ۅ95Ef@ GI̢`><~!GFx^*:ZubHyq4j( kϽ9(@ٶГJX%XJ;(GU.4 =Y5(}fˆI̴D)q5Jihu&a$n*ZӉAV)ciw^J'+dIBΰAqm69cz(X۞vn{v *5%xB .61=!Ib~Opo(m9mp$v&i? fnb~ڹ ͞ j͠*(BVT33~80di g,bNJ\W\Nlo 4.0CF]:np) fJVwҵh^>iT Fcvv,CP3+ At U[pmL@A`>t(`%1ѧ&W A9! ݹR^ m&u|썠kѾ>kE\)llH8 z6]>щKDLS9Qx$бR-\iS&"{ E,7h@\b&`HGp'7e^4үKld;$Ҝ!r]eENU]J`N;J@XxuԴx^fn~QO bLm}P}7vCyl)fd jF8Ok2Ċ-:8Mquf"(#8d촢"8{vBme L=L[B 팥kN]nn"ͫق'y8ⴏ@'V pkaLu+F}kT`҂[4/|/M 4cE{/m\7-m/̶ytU8wP𮃈:o#0":P#`:/vA":$@"*.6DׁCt0{-8D羿 ѡu_E t3ۡu(3L8DaCt}-HD:aC̐~{3_QA: Qq: =T(] ء;TpԡB9tp-C :5CA8t@}DU )P;dCohwZ0'm0JӪN@:pái-vvZ5 X:v!馛v(Қb8j0*`a fp9]V[4~ a~ɹq7-c=cFA'ƒMAoEy l@AArF Zr!:2*@*Gd] Z*X;m+jOYmX0ީiOv,L;wTWK CS.hضѷ2Dֵ]xLN棓m| 6-X`ږ1c4wTOz]}ϋGib /{IxE1D"doԴ' d7Vd<߿a X : Sj]Aю^be8dfLhf5WE!m ա)/Qm7."H)gضTgDArmAWFm }fbnڹGwE$X9%ͤ΄9\4~] E@V3G1T,3z!+lW3hhzS3ntQ…Ozg]m*ߵʤ͑iv$[i;+;<% ^穔Q۵һcC߈mi">uBB7"sDWOq ]LfK7 (y7cލkѼNw4v7/z5^O>)_6l US6C|AMHu!=D=ਫ 8$ĩҞE\0W_@U_dA}5jO*AHM|D 7xAoZutcp@"Ef#z|1zȈ>CdJWe5Z׃yAGq=vX]0mJ(L(E8 K{|4)`) >t8 Cס ]*8itmf 1`%AnPc- vqM"t1s.W`4 }RxT6Yset;P];ߚgyչ{Y\[jH[y'͐Z>Ғv>բfQC\Hf@H.U}%pY-ዯ `qȊP&[,xWPh1葪lyWcGb޽:Ϩ#y|AC$N5t;)TI$h MZ))bG7%2|vn*vb챠r*pH'g"Iw%}=[K T6`J y = AxE}PvY*viyN4niyr1WgN0ʊ)TF4DtR {2ґh@V&>7*G*/-3<Չu \ق ɥۃ9 V/G1bhlc <nV,[1g g~fkӳt?hAV-;(0$W`s\"i !"/׶'}^*#`Oo~7_~޾y}}+W<~v7H=X/?;/?/Kf87^OE gۋ剏8yYƫr>eϳ?a}|Nn~>}yB>uWӋbD[u ɟ XSk& X\J?~9tO?scd$sxGs^~<硺_Ssz/^{6kpw"XMY0Qa eL:`cՊZ^vvj 5nEjۙ[DJ7I/ g4Y:X¨N/T|N!"Af=ꣃ +bp̐xP]r{Z 0 Έ3]t3'}"1WT IӃ29ZҰ$d7WAQa0PA. W3qM0wr7p 1:"ɠUBLT8;O1%`BP_;PO<ċסB2GC_:РV <C8bRQqXƵ91Un$Дң"'*>#8\;|4dcOPࢼϑըgNm }O2€FF+%[%ypҼQÄp|Ձ$g4H꣕2UdK\C2&N 2hpƢe:L#DSC1)E| D`:^"  f T=j$cXuߒ[Z%=-42F5f'Mh@B,qŒfNS*h Oa}ĬcNL5j2JCj|'NVgf +0$(H A[jC^ᇈIT$l*~H32-*L7r RTMvdE2*i"1vI9]<_tb7R& FoA8fgF$qdL9aV!)~/YBF)).Pn2ҏ7iY6YBtaQCiA*Ì8SDL!c0GǩwKZF-F10!$c0-34W7yKbh(T.# T`'}I< -&$t"yqA&14`Vъe|ɄH0D]Is+L#z^Rf;$x%.$"zxՉP]ВxcɗbT\{94/3D4:VLp @FIH1њĀ-I}y,N"N ]* *%[TTA X1D(/J&@u ("DZ @<q^82o΃t'<<"MU dhQ_y #!,iKy‚F3gJ'J[وz@=j)ȒJ2=XEKUHbl#g( H"!g5zk&a@r%`1J  0aR!2綤Pi7UqCtب` FPf$PEcEPWU0μ+gDpeEZ ;:1ЖK \*#݊#Y)|+X=` q*9ʑhaFsLmF!}RB+0:'^(}m&R`O\:­RXaCbI&Y):<N֟jfvfjS(0MD*D %#MSɇη2~&49_HOpEBRQ[ţz2ݙ(ʳ5|Hy|n%f"N̙(lY "XF)Clt BW~E-@dV:ƢOtJa.b 3_)JצzV4t Ȑ6` #_%3< `5 R9&NfӋ`QTVr+PƉ"pTar :1Q*.NVKϘ$Mҋx,8i|Bpw|Υ6|N@$\d,sPNFsF_SԵ^euqafŏ&scyI uIDHe LKRŘ7PÊT ?Bw$݁4ݍ~QDRi )6oaZ\Z}7jSJf$P4I\D)QQC*.ՔEM*@X6Ua+Ț#Hz 'VDU^8DVZUd70]V,^ŝ?*)DɯΣ_L>"bsQ ivGi)UPVUT@Pnxn$%$ZqkT&d@gգv*hp 1kc0 &@ xd9Y|p/`X(y_&&I'-S\+/o\!pQgBmkL4"t{ͥpPJ FL`lulϠ$L@I%5X4;>ɨ\Ц  Yތ de;:l%%3LiS^γn4J`nDT6J5S}#hY&NT #( ,%̡'-u0P\C<$0L0Ϲc4$gV^[IlNEE0Am-WVdVi~5.Og-D460K?NP*&jBP1uT9vHdLl /IQŪs,N!!iQ-iv tN(`\)Q.54(& %ݚB_'nIQ>Ntubqjgx`(hVby1dRAE0:ц zk¯  S$mʢD8V #+MI8ER9dc99T MzLpv&"C$I N1Z5ٝ\|0U^RjLyLm!`x73݂@*6<9XLFSM^^MlmS(^YR3Ѓ(xrHpX5lR˨!z> \6%o܆lA 1S1daxz"q%*@ݳxO} PDzXHo>)!}:T485Td]mM^K4aW.i7EZU+A9}6}MJX[n9p%ca@)PԅpcJw],PWIOBӡI?|jsq]V8و. 3:‡B}AT ǀw5΀:7 Ctᡐ!syTѰQK@kxLrkpSK,3i}./ǯ8o~ykݨ_?Q3,Rfcڏ<4Q{_"@lҊ?!yI춤C Qe,g#4]|GI&2T7^N=~Qw~Y_X&pWqJmxz#~q[71 $k/)k&,tHz5Tu@ $N+ر1Ps˂uO2Εw K\F;eb=s2 yuf/-=gCq⽍Ȼ1 sed3 ]+܉pL/놮 ߉aB[]h&lNA*=E}6xUIx(zsga=B m<= 1Sxp3ezy G!Hj)B>\Ӯ!q\%ASQ% >Fi*aYon"cCw)YjZyu@`Ќ1зGhbԑ@wO Hَ= z |*֐/KS ,A^@@R)GOF!-DHΑGAz`4p70ٖ!)v|rЦeYz$`ږ` P2S|! [Tp *+ՑBuH'Jn7*'=%m m9ĿTm~.dH*DO-wM^$HGZʞ9t6O׶p d#7HZńgd|,*u\n~-✢h+ X:ECì+^O]}%3̕ddːV+2bǑDI*WVLzDÔJtcrR9q`=6ݰO @٫H; cl^y^}>ۮ5ŊR^fC"bfAf%~!uA6й'*%2u ݽ(4穈2_>qnt9cR PB+ԕt G"Պ3"}>Yn%SvՇ#/?\Y+&P 8&Ϲ|S1UJL,1"{c(2@bnK•)y'ė*pvOP3DEVZ^v9Ϡzq2t!-[<@iN7A2ˎ7@a 0 95 L|O X34}w!FH$nx+T,k>I 4 TWU-~~5|֬PW3l5@W\in̎\5l%l2 aZ$ۊBW̠/o@n&~U+.QmdiK1ܴ LS:rl;/CQ`2Y1K==O֋FƶP鼚Ѯ2 ` g Oiaf0%#le>g% 346JӻJ!YDoWplpF';bC kP8*SuVW2#ΙvQJ9a["NwI<%ъ:LJ>BhIt3mj&}3Ãg5Rb|ӹOT!:ik`p ZhBRc bzA 9㞇3T {@BLZ%DjmNA`o>Fn6gj[R ES<2A6xKl 64mW " )!Aï߽98{Q"&])A`Ynܹp-8c1|JJ]Tr#0GFh*˿6ӶcESgRlsQv~|Mmjʌsک]'C"NӜv\ׂPҜkYڱtfN Vu%9`2j^C"$闤HCYNJ }EvVtqХFQH$9\.2jTu:S5#~ UHN1Pm;YN Wj|J t+nSMyA] O "f6s|b4P45''&/@ KkBGd7S'A)g8`-NT w?MĥDh(1mx> H(4EL@cVG)nW~ۿ1t?/O~hw;Q=jh5kP.Pk@x+&h=Tx=}`iLأ#G"1$t ӱ^k*"=S(K: Nl:W$Ξ2z,"%<֭t[@$<~YQ{Q<J_O8-sv !7 (!GvKlerO!R4L%2%>ȝP_O'ƲՏAŇ»ʘҨJ! AzDT!V1ӣ*G_UiTRBC> {b\1}5 CFi8zO{^ģ/ՐVŚJ%*TzVS;evvH{rA7"<$㓚6p; hIK%s 4ThH]%7x~=XAi'unY);[Ni'yʓ-O9 b@92kr'ƍ8DֹZ1C'+*^ix_a#&宛p0#%QL\5xC ?hKa1«{B7-Hs!5.>0s<1Rcd5R'|٠XGB| Ć9i(08qz:_IΦCG$#^bcoekQBxa)V{DqpG,U}YrqTzы9QYEfNQ(JM- 4˸ž!8O4P2uwE *4Snս /#NJkz7s] xɆB) PG sjM$N٘W.,uy>'g($%U(n Sx7K)HLJ`}ҝl5@I qA]jϗxU6^JU^ki30$MbgӪ\9  no+ⲀD(UׯHa2l:'DMHr[1cݳѳU?BQ '"](r ,|)Xe|n:agh9Gy] e9s&_yxd9PtBπcmW ~,E8 5jͱ +M(&BRdƎG_̱6\Q  Jٱܓ TDf{\"a7(4WtAo9r`fl]$%e ;Ga Y3c"׵- ֘aIOp#sC{GR;W?iz [N{qL!a{em 9/o0_a4WCɀXJ"tϚԍ wc=eM/e !EmtWMBecs\E8 m@ydl[ f׵s]p);+/uvA'4ՋzB66H My"-L˲t Edhu?",{W- &db_$!(F8;R0vsN5I75ZiF3F}1\t(Z"]=F=LAU4!2R%-do]HpNԑvUWE6T==]Pfg6nsl>VM@XJk8I1/(}M[(;Cnnׄ<5Fm*6!%Ly9D1~f!27j!A2BvFے^4" ھhB;2NH[Yisշ4E#K+ | ` z],\5?dV@kOx&K'4,KRV5SiŎP0e9L>a%-E+[xZ`V @5cw/ JZ스L-֤2Casm}I-^6EVw[Xq, 2ʇ" Suc*T|Iu!l@47Pxm-i \wOvu8kf kkңxlq"Hiw7_:#G̔ [yRzT <]nbɺ(~:Qrk5 Sux/PFI>G"tu)36ڜ%`J_[t[).H `Aߡ"8枡9Šr\{≯ `so"g y\Hc B 0E[tUF(_;W -O%8W =λ2*J.Eo趞"0uG[Dֈ45(g[*\\6jc18ɢLX n$?# m3AtPԻ< p!#٫Grue)pF{@HZ4]]ڐAyx3c-9lmC|_;7nY06?+(Gf͵ZrೡU8 P|~(CB\$.ioF0c1yqd: ="|*X\QG'|%&&pm&]1`O^Y[̱φ 6 .+4lG4=g#Ka^Tm4ӰNZÍ+399\@5K )7:`3x.N@Q*Pm|6L94f!Ӣ5~_yՑY*=[h0'OLwu;$^C9 bk #'aң`/ NV;Y(Y7+N%zf[൅BH4 QHU&߼azxW %巐 Pxz.W @PXLS.o(䤞=(joZy.`c4`#.JQܦÂ)^:S_$"e$bCh r9bZ8]i#oYOusgc, {SX3R:q\[eJ7 ڊbJ[&wG2vYmUJu 7!Pgbfmfbux4.Q˜mVVJ^V*Pg`V;Fʉ1aEF;^k^.௚p`UfpP-QEV9v{ĥ}ɪKF[[U^4B#v]U|0EJٔ ^uH1IzB>Qk6ҿv6.qs5`@~QlR-ܮbx$HYE=LkJx>Vs`2BLds+:"ʞht#&w]TE;zrF!1\3z(3^ 1VWlu:)w* D:pcpy<2Q@x:6 u8Cj8^쮯ƱMKG\;x2U9kOZ GO7Z ,ܽm_ ">%T梺ᵙ^~(z~`߇>܌#YTÕ۞Tbgf}B bAIn㱪c)X_evOWZlLG|-8jB+KF}xg NFXuFry,:'Y 8(Z87=KҖ[ ,=`%i6৺Uh)@ 'A$$ipW7x>tm*'mШ4AJH [*5YLK "N]Q㙻!Ƒt0|6rE\~P#[JWgGJg.KȧBM?Wf9w6|RKTfGT3r{ l9$Ej.ި#E|9\?{Oqyy((ֲHj @0%[)kNƂ솸gd#ʧ7o"hfJ.x#ւ8Ps5"hY A-pEc"~j *f$Bg4 #PAP"yzҀ3ӗXSE+h$:UN \h FQ֨;kagZ@8zy[h=0ӥEA<(J>PK M% <>d5, ѹ"~ѽTb6TQ?SN: ;"iV|41GN[<4fH)F~eӔRt|;^oeUe )cU¿cEv>Z<,-?s: uBT " FǪyvh}g|Ɔ}?LҴ> dny4`0b1OE1 u;*ןAk9d]VQO?ևU#wfad 99f"=*`cJ԰ ۋ2G86{!8rE:iDe?匴8:OYWleq8Egd',;C+N(*.P & 2at.Mb6AUqڗ{cW{$4AǷ=,u_p}gEDt/dҷ}R~&Q!]!e3)smMO*HO=mD綗3 ϾUlԾ]mhgsi{$}8WpV5#'=%^N&t V?͖vN! ,2}wEM9\@-C5ߠބ"7\)ַ!8$'KWX쿋BDvUc}w"]Tnkoƅ}gy Fޅ}*ÿ;9zvS .M@K:mJMS羽?]: 5!(Nܨnn֟"ǸJ\=ʼnpVW)қO59*T5(mO͖t&xk6f&ABcQSxzPs)ܣm= BvgCOZ,Em V0n0Ho7iX&d<"i.o^bc,b6캽kfr~ZBArv#mA/MfV_vz<7s"`b{:(l'g>šo"qҶmH֜4hIG̐Xu͝y91S&O>Z-]opk[f'|dl˻2 aF//#oʘlsrs/p{$0 ]!PM/cօRvi/++L4v@FܶXcIaij&enL.en ><}xt0Ƕqe>!j閭YýH]f Fʅk=/>m4#); c ,|Cѷ0hXŤ!!7+aNC"J|&}O8ȹY0QUGUZ߬C%jYx[3*:p|{i0~e,_rLO9_ƾ~dxSH-^jȅi/[ڧW/+ nۢXV̋r͐߶s'Sz U4-]Sa{1rW%;y[r78fȄ=t t QFEX&`7.)roK\qn^pYo@4]n~^8&mnSoK[f67˔7p%`Wb#J̿jOr,~b};%ϿI۔,K{hoSZ@{^g^@V_^|ʶ=yDo#@KVCF{Tr9q,C%>*ف'|=kt q,S$=?gõ -+GZJYCVypA!)$,rnwܖZ-V(u̱GsXtM>si'^8?;r\|[Px6JG̟i'#ZA/j#ۈx3sf*bˈ6U2>g願L䟍hMT-~kCz;ޜhM73ȊɉVt-oÉքeo#ګb.~Eآ^.m7;αNe7Kcqz3e/\A)kF *>OYn Q3L?z^oagΑ5jqV+ jez_ =ɪGrjá.) :im'yM%fQY!mUz3sNo![^tL6ԕ$ԕSp;EmSWÒJ)U 86CdoSWaeI6uAg|3@>]]i"jȁW]]m%7WWJ )ػl{WlT@Gbդ?|^Ӕ )꧌9秬&-<1Jpyy1ΓCJ/&Ttk>4͇9nWwxͫArfJ "}Bjsۼ|# *ja!v~=$(M4֚5ct~FHNp;5YLJC ZBf;@ 6^Иiļy۝}_B<#1nX2`-Pn{::U oX$fCR/,`=HbIoV6 i rbJ߬`J9ʩ_^jj6dL_էbPL*bN?<`yt>(ov؛,0Ƴ(K #}{S: ^鈆[JO#֟^qP'+[U+onX?UCSuWOO^`zao_Ʃ_}h٤kQz{adQ/a9o/:suq?ާ|l< ȺN*?t`TXHTg5EzV>$۽k+o,o`@۪+Ӡ |IǒvgS{!mmCk8POQǸMGٚz^-|a1ꡜngQ>2ŋ@POoQp}.-TA0TDTcW4ޒ4Hjmf)%<7K4(!l۱+=Rߌ*9ÑZJ@B&QGb'kQ."Zz38|{NWi0dLF &{pYXO |JSFEdG*R3c(*3\˦q[PR,+թ9~@r, OHt)ݮX%0?ᤸ&aA5z;qڛ#:' #oy7qK68}3"k2}#^HO Fs`Yi"`m֋h;.gPzWD2W{3zVSg)Bȁ]rDc:;2A@QiS"Щ1KE^ ,v "Z}W\/-۵bEmZ۫<~ OBTV8,gB]z CHV;3AUճ╇:!u/l|3" F&gɆt}%7iDH$ڡmDH4sрп*NC|}2 AU>aۀP2(ErԘAc4Y)ur ۀpFBK8\!n%JbԽ /׿%VhE?lJd\Ղ/oK7YY @Iӹ@Pؚ^b~EJwL?MQm&I{>4s\ΛI|pusDקIY%ueg&<^8Jn+F&<-DǛ7[lv350HƟs˯/K@{ R\I'h! u{~ZEչAѩ;m (\1-g%m HCJ Bno@7VI*oրFHSehCiXF^OsVXP7 ,=u(YPr'@}|Dv;pJZ˛QHP!酻i@r7'@sB ,EŚVQn#@g!aT N @ n@@\P)ѳe;* M)]"S|3Tn #>/ "?> o&"T:f9F[APQyyz2E! F<PU/Lv>v%4o7"NH&37qwC8[/?EDZGc]k-q抑5RToP(4 Hco?k0sOn84/.+o?OC=NXe6C('(rig7O?QP}(3Oaۦ",>8}NVXs";aov4j=Nz~#?x= B/z3GTvo0ea#_Tz_BoUsg m,7?$5|_ȫ/?zP~m?b>ro~TB6~hg//z_tt ; np{O!ʰ)7?.:sqW;^7&5 A!gT*)ly9 *@/s!]%!_>wO~W|է77MmU(F4 e#+b^m[QlݡDeׂT|{,mF@%ĴmVmU|]]t0WǶ~RWR|:(A/Qs'!f') q6I]3wMG ȱn $P-q _p0h,mmsdvވLjioF&k^F^Bص.1oE[v;_FEc? (^*zBCmt]ͧk: u\\~>`<+% S,r3it۟1 gXϞ:>P*Ro8A|yic)6RĦqQ~A}!/e)~|txW6a3Bfu}[c|: ױiKN˽ /;aחt!) ՗eNbe#S71s#clP;:5zVLOV x'_` PLk(: f zZsxţW{}q>N\pz jgm)$9|:iC%:51LvR_d[|\i;W  B ƒe.Yh" 7Jݚ3gW5IE8@{]1x}O6{fJ֋( r%3@#^ vhu.4Iaڃ=׊f]s.RtD fMCe UHRbG耑TGk#mZ+SP2ɒVNz jH8Yl򉬟vkШZQshCQj:S``n&sK"厒3V@¾Hyg_ i𻥰}M [ ^.(R994u =!ոO+jbⴕ=:7la{H A.ܮƎuJRWcQb{js3m]'JрHg }nRX4qN)ZMatAP)Z1*Bg1UPb̓&//Pln_ÌM YS- `Qト#EAu8yL+Z3txai9n\'H,=:vs6E-SMsۜ9H>4ϳsAm@xea> *FV_Q%zpX#;9omT=Pk0;e|; OdȪAr Ashh |-rN(V X )ޓɸ_&5OWY=we<V]f#MsE =\r/Vʉh,E8evG3nO % y*r!Rqgy]uN,} 5zv"^R X ,o)Ng e9jp\xw} 5=rE3.t~9 iD1xLL!7PT+U_:\yҩ4'mq^l~ܪF#f r,@ǔu qxK{E>/:tO8(? 6C!ϑɜ@x/ UGQXpWrKQA-ާ_q^h}+< ϫTA~ؠs{*2|HĚ}&ԓpvs]d8ZW81^} m!`b}j%e>4_m,!(BMa.{sr+dikq̽k,an*>Ip5N_Kd~3=d0JHXJw !wkP\yy*Zv\ƜK+kѱa5 x%| \җX6/;ŋS5KEo~tWjllHs͚͉+Ql;uh }3a*`B Sh __B N˔`8b7S㝇A~4@w )c~ӲoF ,\s'n@Ve.aZ%䴌sxQ=tJsTe/HtP`fL6׹/(>?,w9-ř[D'Ӛ{.&aِVOPfTt0Gͩ #V1ANqͰV#ujbyv$dHj/'v:nYS#A e5~7IAaP$vO ḉ@#KP~/R^;gx":m(X&5ZO9 aLK=ȫT&"^WW;"$rvΟ4{_1dta/Ce C=CXԳd(qpn9E96rltW& H@8||=zBs12X; )p<Π3csaH@k|_tmeF"-u`wf~Cd>Cv}hTsp!ò&|2s >MRMe$c؜ S(RE5[Ŕ9L`xrdωHr2-IDBCAȄ|/|T6B?G=Yr:C)u-. q ۨ˶ SpSφDlz(%L')@1|džc"/S]YVJqf]8 x *.x{#)W2]x$Uo^z JQ`;燵qYgk憊P+P^OMYc5UD) ӽ5@|ݩg((PHhꇞ4d5 V>$*:wK3tj0;17yATAHK<&pa6†N8N-KIS9[L5RV/b¼=)T5  L#Tu9'32ef76L :fwD9;ĝɠNٝ=9bvl rtJY)@d<_>h;vg9W\ܕv_m+FI!)VCBR$a .gi|k+c+^ՈՊ`{;)]w@# #9iRB$mhiO/V[魹O`:o W1%t<%+آx #)@J?t$I\ |}Σ m)6)Ƿ892sxIbAJHGuMS;~ Q+!9a Wda6χn)_-C^&2MX$*$ΓNdr] xRki\5c9M4(F5G'sZb3v`Ŕ3PĠsUdHl.9}g=B*`[8H+:Ű_*$$sGxnj a65VSC܈ebF faGI2?cF\i :cu-~r0?fבp*0*i=*y kyYԛhqrJb[P ȥ6uM\wc: Bc16FHO$hr92Xt Cp;DV&6b)=ϧі!@undF8MlXW7 Ҵ.P  A7NMgJ( dCzm2̽×pE O~.KF&^.97ad xBlUs/}anR0 9ia+$S(J7`V7Ij R|߈S+vJ?0es;E /sI[YJz웾{@-5kFe<߽@1Z@ی(S$æ\@$"e;wR4!Z9@֯)i `{\cE08KӅz9 X#heǸap9+H>^%;Y62yYN1yT&# X ,1~X?=]sMr|nYwoUJ LƏרiyjC3`dv)#d١\<7F3#n $퐃3w3<9R 3,?5OGH"T7<}NxrxeM^f*!~"Rfi:&v .ʈdգ~I`oKvx"m4PĶA2΍ε7;єjb(}CV.dEݧe013TV]yOH"$eK!k0"7qŐ--Cfh859H=R 1w goF*l]!NPx9AƱO9ƮMۋH> |xrʹn^l!כNqkk~a<`3Huq.3.l@?Ѳ7CS(Sb&=:AŢ-3׆F&ZJU:a-H NL[8!)sdgb"g2N<DZݽAa?Je f͏kGCA ʏ=؁M:Qkdg YE yhiHG#b;_ЈY!CH tR Wy&Ia,i*kf؂i9nhaflxB)o-p5sCfg1uLYro_m8nDuK15;n%TXhO{`JĔ*nj ,2I^㛊g{/% ",X+i"`40DQq';;tZ@t4U N%! a7߽# &Z%ɈBGgV2wwuO'>&\52IMj ۴ N40Ooylo׺\=w2-EU@+mMh7 L;X V*31;;eEV<&^Ě|X4Tm7{ Hir$^vE3ųILm 0nK#~;љ1׬St]c?qV<"I2֩HcNبj/"7?luםQB'Q+ڰ~,=UW6YF1sW56F;{M8h[5g˜]ڂ z|?ȳ EcẤ>o&B(BB"^dԃKJ 3 = H|Ty_\UaDku]wԕ«ˈ#n5}i2[q^ nNd!81"ڠ^Pih#'tR@ rNsӏzV0!%Zd[% M0GApС yT`Hel ZAQse)I)b"%fMk0iҡIbtbRj%DH%E `Z$wvZ!rYʰêf$.H-Dje|~Dx޻&\.==@Jj{&4yۇ?)=F*lr*NXִؙ؜)ʅ+JH4_Ar -vcK7,a#&u^\>_m%3+5 9f]ɖnEz0Rvg(dA<zѲ֗t<9qsq,tCͶHf')B(6rg~g DY iUd&/$[%nD66>>#x]p1!:&{0#t a(V}\ DQ9f%||A=:`S/"er%@꒩+N$`$mIC"Dn*6D!zp̢̲Dc1r`)?T6D d~(31PsQB9oY<\T ҅t>E}@z]F.2NYT)|Tc -W|M3H|b^ctDz41VBg]=R $,F&=qp@i:CvYbv*. 'iHK*9nY>v`cw4spaA&e0C-(]*=K5g1\Q *EΓTǠȸ׮nP%Q7p,m)'j!Ȝ(* BfP3nP/11$' }H֜}ckdqfCHpWމD2pJ?@YyKrFOMsD1wP[.ğ)]A8wcN D(Opoi^0_.(zq[<$Js@ DNSBalChRimiY ͤHk)G\Ȃ|㻦֧Ob('δsƃP`9 bnN!.S@ܜQsgG߭!,Ҫ68'iAI]&ȏD(}7wih25f7ֆGdG&~J|D(qsR"ənݴ/ZWYcң6%2GQ!8'k%?2_z93]j꜕@\W#t'Eon 5<-Jã^csSP!^;ο_Y[DlBS" wt4Cd0/Ng)SRsxgbqwq#z J#m$D,;@DuK19jJ"b_VՑNpKӟnDб! ibB0T\o|s2{W,F/jUHty Y, ^JT8M"j``u+fTs*#`g"TA澳m}{ A$%_~E)v [>&0Xηw\kI(?,$:N^gH?Ü#iN% L'p/ׁX.K?rNlB, hֺj(dEܳXw{t09;l(DF-M 0B]I!1ub$4#lЖ۾lȚ29t#4 Etʯ%HU(Dc^(lH*^pJaiڅ*݋ZYvML\q]KPbq߬ۮ.u|uc@m2ΙRYnˮn -0 X!oƘ3UkmՎ"#f9AQKjo<?mޚXGUd~N~]rpQb?¬MuucIw+7f^10dw #Aq9`ֳ|.-޳W7k0ai`v5Nޏ[4`BDIL9^`~ed =~ŵa:DtNظƖ#o=$[|+y\zDq֌{%э*|-^5\{6X 'z`noG5(Q%(QpKsS m|f\N`_H6k՝Pv0(0^! ƂNi zWw`M# F$F@0 e(Pg@@!pz%q("f؅3@r^ŝ]=䒣$Q׾|@^01t~]ԃDi l]%U`0IFZuv:wxhO5%kX'#b䎬,U/`9aےʟq.LOq{WMYjI4uZDSA$UD3G)$* DǞ\˞ei)9ƍ4@Y7V!ba 2@yVI6 o4kiYAVR'ZJVQ;R1Ctw`=xBGL7)Fށ%"4ԫT*9CtñlY]Tܠ}KN=cG"6}9U+'bЀMwvyˉ=Li`"΀YBAk5ޠ#Hʕ|"Ex ~RlEʞ1د-7U`gb봴LM 9p(_u*|[ ԡ3KSB6zBe*=H#H4zQDU|acbHP̈lX/q~PP>Wi?r.ZəQq6NBtFa]~fn k Fj*&9ޝGmsԕ؛U$DAƻZǩ=ptpP]zL wb)-b R`,--*^Ɇ2?Fwz؅qF˔@ !C MPk(A&01(G0=4i"7B0/ .E=U%j5gn`+ފr)^[xbU=13e OJi!Y" HUp #yڼ!"wX% yN9bxzNEHCKT׫djw+)0⪧jj=NHFc|5L$:3m +%e*Yt,8ӽNt "e8~",1T]B̞2aj/$"%QQ7rF#mZq:)z( LǁFN2y0+slɯ6ԣyIkZn(gFU}i)[26ù|?eV.ƣCS#(ٽa*"gV-"2ÞC49`ڣrWUUkUU_eCj,5 sؿ9dcqBu:8ǂ&tNL!]d/ p#[tIKp$y} mt! `}L"K&ŵق!DUDՠd5#L E~!veK6bQS`+Q; Gz{2X%ɝuS~%}h8S[rG FΡ烢S Ti+o xK9=xm]}G `<4{ DNȌ0tv-*(=r0ZVJ~'ipSg]kM0 p6H65w_=|5(Y0ğe+Yp@J\o'uC2h*hǝ$58fhQ X잞}5`n}xSϚ]٣ˊ:HW>'2S3Ȼj< &IyI&r#+tc. v; J0N qNc8e.9EAWm6U'z`J&c ;^ZsoˆLpte"&>n{ǐehXZVL]„KP⮚`P-+`ðR5UWfssD[jȖ+R?55쁖SqIk5Ua =:~I biV WeW|ɪK9 xw$?WOD x]|!HS*,4['_ދm"8Slms]?)pT!$4 |X $%!_ ش{F'_UO _7u^!VOm>'(pkRKʴe{ oY0ƾEZ,ll`TH~J̊l"ƒ:#"Rw%s`bhiL^<L_:7w R_״ ŻGCՕb2J)Ê# PlkBY#2)'jHmS@fBo79l`o>M,jSw2NRF nRf>木;h1-̄[Ǫ&~{tϱrCOB䷶GlC )Qq'R MTŸ4R^"NrM+%p BT+nYP8d;!Geʓ v"Kb^LOf(eFE^3lA~^bZPrF(fJBx_‡o|b,;?.>u < g4b/آL^ˮɓiʚd\ n#lvFqr Zͱ )ʝkZ$sM7զk.rl BxJxG b9 Z(8mbNG!&(NkZ`)sO.E%Yq~OvH$@ H Li"S4zvSw '1TQ.\a~*/KKP  {}sZ@j 잤"lrֽíA>kS/Phjr 0w`1g̤[9{{qdGRr؏WѓRԌ60 .Oi'<SWbIc%Y~2)jii`W5ُ=4[19 %m=F&L=h >N,hzoô1dl~`,U!5 /{!:B > W2u~@*)FD97WyX]b?շ鵷?)e*(ZwΧtnrsYBDyYWظ(l8dʺ'H*^ I}T޳7hb׭n!'U1<ءW , Om0 s`&zvQ,%][/b@pXu~Y <3aSSr|[ZKA9Sri?PLb@Y< -lq;ֲn*{QoqG+7 ,eWQ/ZZ#g3,~(.a~.@K,zM&4t8E\˃?G=ēQ *ش$vh4uUBE^,U YjY}[1g~?GKQQ(pGY>;Rb}R0ܷ9"UNsiBz-Ār7/0 D/% Mm[Ҟ,3Fɝ>bj[0>To0T h+ˁ$ e^3{J|_Zvi8Oi.X49"CчhfuD&HSӜ|NQ\&+#V[yz0|ԝqƒI쳒Sq(nWDԠ lR86Ղem͖ hKwSa#ԝ̀/an}R.~q}V+s}G7Y:!"m(J? rq" |YF;6'xJR\UB|k{oW a.vMcAd"R%^*H U Ye::#o]^L/n1`$((K[2寵$!zx^}HÊյeԎ+,#W^q  Wa"KG1)qzR'X- M GҕշM0|'bH_Oq9Oyz_~~|oWÿg/JkM/" ѐDc8 %eP!C7RPCP*oB!xO%i"WXPa.e.;!>y +Ib˩;Bk>%ЗsH4? $&I7,m)1pƐ$vM{I ABb-Nv@iQ% 'XpiaA!VIrK"#ABfKWi~,}y+} $E( 0"6wa3exb]ld@BV,a]2yod33:( yq1opb`m(.߾tnS U*E.o6\3:sO?,=qcl`śz׎چoE$?ĉb&ӥF9ߤ m(Bo@YU.`Qd6`{eY k ActWsZAm2^D!9jrZ@&QHx|`7%_uJ֔x>stream c!U4Ö8{@ү F uoYP"pKFU(\l> ^ htM*OPx9Y 6/d]5r\>bqߕpuY_>-[ȳdz%2 8ޘwT`}ݎ>?ţD^T_ߟߨ>_#Mn1أ#ܤ#0sJ\ Tct9\\"$Lf^vQbAQjRU.BA6r}K5bUW()*;qF.\8[&nr<. t2E~=iL>;[ߧ`;c!ַ윥nDJY5mkSGtOv߅԰<ڛsx="U1tuEg:ҁsEzv`bѻ@vT~(U &nB0jk~:/ixIujY?98 ]]e专L zuIN[N.9uUY*x{ /{Tno,t-k/&Rj~%<\΁5BSu=ae u!"#Ĝ&5#!#_WNf? z kKP}ϧ"FdsHxHmUq@o)]/jhKh!E`.O%f.W?.jri>Br:;گ@|N[D҂))Uf껥059xaG!KHG;T7 ߉ok[>khֱxTZ $}`P> oVo}:7= Ӧh"]@uk80 =dF>V #~vlO AмcA >> { -{4teNI}2 Rp7h4CJ.7T $K$p* ;G2#JGwy|vݹZwcS1r RҠs)(aGb>w$)+,#_H 93:/!jw^jy/\@E {iyFK19]nPKUt-x(gGDR=c ov,Ju$Ӽ$UC'υ(RUdKMU"!s|Pպ1!yǎ(E/NddedKhҤ5l_ΦT[((g.GңÐ c ErY!' rZXPV%5`'.=n0~<Z({L^{IƝ@|% ppa}7(PȵH mMiA= xzʓY;s 8<&aD{$yi1-N>({-{Nr4P6miR {%$CHQVh!.pUJ*KBģ|`O L@# KRe~P-GL{K$G$Ӫn( 3Ҷz#|pTx &zFtf@`@}`R1@ PC]^"´JN|2K%= sQӾOwā'y7P[J}rl# Y&S/iCMb J`NM=ߑ0B5@|cTE(U~y(j~27 121X$3 O*zQBwHrPL(Ӕm p |Z2in8IT3F2U]SlqH"èH d-6X#XϐхEw 11%arHsJ]mV@bII}ƵHye@dkFܒ\pL ^dtRJ)lT f>YJ֤իk"b7nD߻Hn_'pBq;Q {wD=~`r0ug*CcP@;J`hz96Pr"GC9) 6 ) Kg_hRD,3=Uv i4-=NҀR0uOlZ%(3 ECF-Xޡd&ĭu9?!i}YG/fḰ<.XYǐe^ y`.NYv X jߙʋ?` %ϚJ1 fXC8*0Z\Rtϣț`g^%`,;ouK9ؓB`O*cQ "`"(9j&lu%~[o!ԝݳJtЦfww"HPvZn1P pe0C[M'AĥD3BH{yȄdؽ%Ь#SW7[8R*仉lb!ou3d[|;RI-J}(YHlZշ]ثK!9/[tRڕ9FRxJU==5el#]Q2!N,jJJQ@s.]b^t$Z)eJX\ uNHAtjT:!X0o>/I>^BQSбT$;1dsy"%*()Mvq ܠ|Yg[JqOW`֠pAԱjt=4[˒.g vmfqr(.zȊ.T8hsӭ6c1dVI,āӕtxo ECapV6[TݔY]l^áĖ *1Hwr *sc!J͒~cc$,: &v=cCW#e˅[Qːt/)j/!!ݙ? dXʶbx\19BM7"noƴY@f+;UX|'9(#SϳeP vC(¢:,>IUyWUhbZ*7׿1%X^%-{.RbɠJ26}|Vr{ݧ  66iie#'-m=:%Hd9M瑍_2ֿk86v:|P;!)Ջ|^%N;d]>b+$M:O`Oi [^H곀v~vziQX *ڢUc_!9+$YLtk.ZW$b/=T"Hp;blAKx g%ƋAz~%ņP2_!/ !%/r]f'2G B%aj^($ )@*%(OGWFL[^RM-;=J Td4X^hQWB*,Ƃ`@]oe@R5$NU[~o"ހ-N@`)6%m<\C=;ƃ6krƬ 񊬪1C301N<=}|<(RRw*VC GAaE.f0ukn@,=d-Hn: $t%2ěn"ubZ"u;d{TzQe *PҾ{qe`Ėh=UՌ)nމط) .X;-4'$x2h\(HZx3Y}uG؟*s|۾PAζ;5["aFT:%b( w(>DlJ8cN$ sڲ56r!9QIo L@e5#Ba, q}:lx;{&4B1pnf̻ykQ #9XS Wn#nA} eɿ"=~jq%KHfM M芊T6I`|#G4rP(Wvt;NlaQ{t \DwG6[9 0HÐ=՜ŽnJ80^'{̣Qџ:+uNƌ\H̀"EF-nQ$6#Le=ԥ xU'%,#>>(x%鹚 X&^S uY<)0tq!vgAMlj<%!t_@N$@kVJ2Ɏ1fIrD*uNQ3L1V8HnOpBI)ְؒ")rFT&)N7毤ӡj?0fx`z'Ez a:>:\ٳ%odՑJRl䰊C/!+P8)=3DIs}W_Iׁ;ğYGLk"սKn`}E7`HҼUnDl9"W;nQ* Е|5ϗ̣=\+ [\-d ?B*@C;qqj̸Ƈ,o  *ɰ2?}w.rKUotr / ;,WS0O &q͡ϱd+$x^Dbމw9_N~(fN`rYG)w1hV|V> `~R'cԙV ëڈr~rԺ% 9tM%0LKT `2U-{g J KV%.R2:`\(tYqH-(yb/bMHFa _&{F,H9LLH 3+sЂ)>OEޯV(R<5빼ֺ{;4+D$GCܕ^KBnj 3E?ҥ2J$'Ͽaǫ9~,C]Wюv~l'mMIjU2],@ʹ08.D@Jp v,El eဧd$ } ']H%(g9";A}$fma h+G Iҩ@CX~D^|ڽcvTk<ʨU p\iziȇ >hzB_!wv2izOz"9ٰZzt!@ F-y#*uO=YXR,޴ sCoq3[ă#V.Qe!r7.<4˺]{$)w*k5=EBlrp4|/pxu?a4X5sl>}D!jdAk}~ϑO52;z2ݴ4%hi].kws5!4QuB Ok)oHT4y$F{TG:6`G=v$o. W%vR)>=HE'-y %WRܤ|{{he'KY22 묧+c[xojx;E=̂*>խK^d>rBQZ;2the;\O̫b~Ӂ{3҄sV.㏨ [~kY}=K$g(<57<@}׻h+6r :&rחebА)%/ˏ3lz5Fo֒H$RxE (Nloa-u>034s^vzL)\#P9IbS䜗.+.c#K z^0aXy$;oVG+ u*7huY9ȗY%6l9MyP.lQS_x2cgTt+Sp"wb3ܐ3GNdN>!W Nb|4\Qaر'E8◙,G2#xﷵC"`_qeD~IKm5*D<`RƒOV!@/Sk6;6xO2yQ/?*͊!t7v3t ֥x^yO5fjUop!0rs  |HY+8XW]y8a zЊw"DED؃ mfy9_ ;YDVqG$7Zc͹HZYh»b}#(SU.lAu s+Bt4KT^n/Ghy{D^ O/V:*łK`v Q^<`~6'b2fAdLqXx為Rۣǩ04}Q@E-,c3!:3I-WTYS"~\Ey2D_!{{aH36f5lÚٖ?^Teɥ\~DԂ<>ُJa!&zaA-Xpw)£o%p?AqbIhy H$p΀/Iyb5,/t c$ ͯ+`=id3}ZmlFΌCMfLe"_h.U+AD,)Қ3c|A8CXLn)w,AIE%=n 8ߚMn9X4 d9 pzbÚT8pǶmD$5ÛۅEĤDR[DX!9%:(<[Wu|aTa{T M,/Z3^=.?~`ŇӇ~ $ҧΗ:g#Ili0DWk/}Nu7D.CuIJJB L_EZՐYXCK%劅Jxlztv V\Mj-p-e8m|ώsisڏg((ЬÑtK%I쒝Fbw옩iiU];֕xv&; 3u&|Edȭ |o@D+{ks'rC~a:(L5Yu&40lKX"$By~o!Zـ.[-OҍdlVkImIX6WF, P?Xbr?MR%~. 0l@_wl8&`Pb;9GX<J+cCA%CV!m! * Tv8hYx`%7_$k`_qO B=Sl=KY(Y煂j%wdq!*YP"& "~PvJy8fʂHiA9D :4. p , TZt{NL1/hъv!d F/A <Ћ+{T'\jf 5:$Z}BO`Z݂qlM-u̱hKoY,k|0}QC!,K}ŋ })_\z[3R_k!D)^n<%XE>;8N?[ O)~R>4C@ 7"AdžbqXjv|['6i1#qvQKwdMC9 B<"h8v`|AF;a.4}%\d W?SXc2 ]QPHSU6+ 7dSP6_}ƼM"_ԕUrO;1G5/Qձ]96 28wg\~(Jlq*Z]2sQÌ nMbtGx4 \po櫾[sWIX'nh)uIW]˭Kd)頃j*Rft-@;>Pr; WtT\31^rϳw"VhNo/E?v抍ǃiHU~ ͿײjY VNW8NqRYs~7^/&ɲ ^Qu`NдptRRP[tIXþğt-"8hTesSț.'pf #6Oz!|"qBL"6(wSiZx #BߏV껛 ?5U56POT;)߯@ x,`RʲmZa&oaPC-eǤG^lޔaa$IL@إN5XYLՌ'<,Fd b ϱ^BXA@DK«)*QX<cux~wV9JynqM@tZ][WV8rbA˴^o=V~(֥%YK IMϫJamaA EtڴK:=+[Q\Ezhq=z\tu ŊšE>gdR+&S06۟\M#bM *,giC̳ر2@LvYx8I/;zrr[mHp Aq0 Iu-8og-]\Bh A y#O Q)w>y1$V =+|[:w-\G򔈃u '=Jo]Rb Yn%;bqʑLoU^.$6m*]ԂL($0?H)ԱZzHPŤC^4[} xBzuzXޞqU(.d#ȲD$_s:D-f[[PTv p m4N +HX.Dޜb4](ŀ6\ ĺJ:ç!9OI"е#$|k){acYn+w w+ҷSgS//o~?~?}?~W|̙۟~ۏ~}_ cDoW//5p__7:44_oqYyFϝk_w?o>ڿݾ*N?ۿc{}#o~wb//W^*M^oߠ=7/74Cax5yG$?|N5`ȁ޸$QcݗEYߌRQ%ƣ-WFދӨ]=/?ۅCߢ_5^lэܷq|ٸ||~su`Yh޼:Ku377U?F,ױ8Ս~) +oIOv49;aϬps~&Rѯןc*N# xనiF^~?O֯j<q~(wGqB-N=OOOuAߺpv{xh$6O=;ωxW?/ EY^>sM6g#;фnj4O#/?a%op},|?ZF>m޻Wt!|r#'LCW|W4A64B?ѳxteػ-ήFOݗNsN-#-Wo 3,9HdѬGYwY4;N!hD'֍EAW.BN-7\`ޢ W4"Aw n͢'"smg^Wo5zP)HuF=Vǹ7'_j6n id:ţ  7wO4ԲmxG)1F *hq>xXxc+U9+=3-}i2UpzX}wW [)X*sW*C'ȩ [6^> k|nl~TqoB&A@FW*_i#yBƕ?oWH9'Rһ Zn֣mGWb\ r))?]e>_gGV^SO˄lX@$IHh֭hz&7/7v\[~'&!̾8D#hlÅx#4yEx7\ ŅZ|rEcLsd޷zx_{,@|99hxh}3SE4dR˾p3\;l;x} 62>o&f6cEs|g`+:W?w L>f]6_}{%q8CWϛw(;_w!T4zÊFعz7tsyi U|~^_1s0|oyAhQx›oyL%o TLR^O~3x/The{0b!o"~9t:{6|Է<st]bUhDorv9jzij_'3иG[R9Y_\wskf^N1½8da~R_ј|ǷNboZ6 持K!S6711lq܈sCt?v? -.h$9sP)ul<ע(Td$_=JYn RLs֕{Ww.`̜^u ƙ:OK5RZs9vPH[X/rYsl;ۮsȾ-#9`2'`7j^Bqz jCdHƓ92lU5 *v/`=N-0@[|nأ}]]tzb5tc<V/9fhJ'JX_ %Ft<ȖsھηNz_tn|%浡<+U,䗐`\~Es+9!?ų&|%K1`|-3->JްI^ZǂKE*mא{7LғFƍ9$FѝICYU-)ۻ:`'$^9AKx$W9 0]66~ίf P,frַex^4sƣg5^6\+4ie%,u2RW~L3 XsOE>|fSE3Ot?}>WnGiQ HHx\sւP[)yݠZ>E4x 3Fq# |JY̶W) @~1]OI@b҈pkf;J76*QOiOrrβ(-꣱('#8J/>Ss~ܼ/u^88A eg~&rgȕƚT:_@Q`u<ә2a7vmy^z=W=ߛa$scrJɿUuS$wzbRuu}f`]]ŨיƳ&8'w }>s|D}y{JWjL+\;<̃g!d~ͭ^gecW'Nl\Oq9\M.!=Lw]9(:9`l}9 -^sCT 3AaߕamyҲ#s>aCj)ˏv$"#}K%z 9,sQwnXFlTVvsI6p^ō3('`4_06"쨈O>O'1f?LW-}trǏ)aWȾ͜2ءAn=NsIu06r؍U Q@$(|HgWw;~18_؅Iگ^Y9аOx' ٱN>~XZL\^77uʰ}ͯGV,T#jٴ-Ng;!q~:Wɾ>9YJ51ύ{rO7#č-SfƲj~0J9p5[y1c"ĉ,Na|Q/81Y"z_P8ГxN$28ņMyrWJ;Ȱm: \649-'8\I` 쎝@uNӅ:ǙNr4 oPI< b r Ԩ83_\`.Wfa*9żu>9EyRQx *@XZ8+ɷk=NM8r }vȮ3Xn T k5vfB.Y7Eqr$ГgCsDxT#s\f(JKSD>%=?+<I@N ur'yxH}~l/Qލļ XʴP74Pyt߭ѷ%n~t8gsTGEwѼZN X*͎UY$@y_u1/uz-0]xў:@t{Dp7ְ3C0@4Ƙg; (Ǐ=-<Ζ>C&?K; INpAqI׳|١Z d"궈ω}竮䍼l@c$u\)wŐ k\|y~:epF}A{2DTdSe0ӝh>v]L4E[uW1Eh TB$Ea7z.^J0Gn*uZw,T%},3^rP`f{L\8MMjq_QO;ά뙩@g78Q g_]Iq$>: U"l'IDKkIwz.@~վq_l=g=9"#z2Eyw3DIHe H>R!=\22'.H1EYH#I#q]q]Rs`jMx \&LRDgŕWҙÔ,O c9({δ/%>6DEeZF͠!p5yuw~r'/F=MOLyb1Ȭw60Nq4I.oBl[YsE6Ƶj~'gӿɘyQȧ49RPjZE,κMgdh]ܚK9_y}1f)MC3bZYWAxNє:|yQ@\okDK HϨOw{yaRȑ(>Us Ֆ~@`; ժ'rC7.~k3D3Sn!7`IX]^Xd[9"Lu".? .ye-zJ_u=o#|w"; lSCjyݫW^ɺ0ǏJsC2h[Y#Qsߕ4ۋDӽN'~;Wj]C4 :))|NPCdpP4'4{{[Oa⩞̽jI>J;{2=Ӣɇɀj'^J`A4ćum%uhG먹*7Djl,npcw1`|8FF) Fɜ @,qG"cG{&y5pҏ =Ȭ\ J)a&qp TefsK 'I Eos.{]fuNqp%43 YzgYC2#QسF>e#_MJ|:l꩖ϓ[PS5cL4Sq:}TYLs?H%r0R{B\^b/ 0!{ΫRםYVAM*0șW ^["м؍|f%$%8Aoډ]syy#jB̫qʣol5X1ӏzn*FM"zky?e|ͩbǍ=t}e!ٕ<9%>ywH`Ĥ+O `k^hϪF /~AT ghl1&&\N:kc`j >͙I:YB2#s=sW$hSɈfr_?t;Ql+z| A]4vۀɶyҎ<H/GײI\Xn{  -OD+|i'؍ϑb: Ѯ%E9s6y]ޖ;?74 =%㐓w~ x{ NL8Ac+'Iu]/}hm7 %h= En9O2湂}!hIK3OnTy޲g ̈́Ss%]Rnu |/$'w%o=Ys>=_?Âj}~s~r/LnNK)N5ZGNO1$\eUܔ㐈Ɋq/USufF|j΄6=2T)S5QK!U5qc/Mn+A4K Qh*U) U+_ 3)x&ƶ^8z\|0XM +]k16Nap09<%4(S??x b3YJ&z4ծp~(Cs01RT{}ny/D4k]i暴#(Q~vĬ["o54\Va.)FXڀs(ͅI45SCyov؀SE8d-(7pB6)P*8lYbPFbT ZJЋaDVK"4 S+fiڏ-ljSBdjĝךQMq>5uh4LM"/xĸ>VyQ1( Ѧؑ^c 1Tcr3qT^ܬ_V_k.Z= wtdU\"{|K?D,r~MlOn֫q1 iy`Xl&eǵ+6Ӕ0;h+9>Si$0'Ԗ* Ƅ]br pzMnfjGvXa\-)&5h2W::Q{+5ybۯi5dWQmEADeudb=spWq6X.-$5 &3A&O\*έzWF{w˱b9(Mpj*JFm,aF5J\V+aj=4jJ&R&V*@7 w,PQC9}a+bOCO\rm_q_9B BXT{SqbwQ_)Ébyv7}3m!z*< )bm3]%Vxp(ZW #͍5g4V8[K=5֮2 5Xdi`r^lp[͵FB?TUtrQZb' 12XZl oEVԻoZ^ݶ-$^.>mpz]}zM {LÛ5feSMY8(X(lI~l">HnVUZȋfUmK`;4ʚh+w3Q 6x 0oryB!(zk (:pePT&,\TZB%$ [T΂G7 S&W|F"OU1_(,&k nth4Cشǥh\aB)ф&"5ya2m02j_ϴ[8O2g!xaw!@Nݔj-BV:>}E֖Ŵ[lkc^ax^q6UfΪ$TG V8QbJ'4z]n]5 %!]-&b baHL($pc^S Ga(&3VúH(&VQ⁔;n[`ņfch/㴇zbu^L~L5?LIwkU?h&f'3Xx_=ZJfFڣd"¥S"Bm('[\c-z[A K" P5Z⑤ y"Ý7V:֌O0]^8Ozl^ji(H :i̶P?@n"^Cs)P70]ۆF&ڍoɡFJ-i-L^f*FbRj}R6i B,p)d@d퀠MԋT7z FbPzM6PR͓g C9L -)"=e בB>mzZĉJ i{<6\m$gd(cԛ`bk[l"rh4$AaTF4#D\ 5 4h*O=0a8 ]i} !ڳHq r`o35[iPC`fu2gWhܼ9p{"@odnd`?,/)`8l,037u^"CxY*`:Ve[`#Qſm ڭrrZ+~5VC@P,Z|΀85Za P|oي>p_ |A-\#sKX_ns`>j ]*ڀOEXt-Y9ȸb &py z/a(=?Yo 6h0*c8K@_䡼?z~PH&0c8=KJ:%(U%CmcN~__=WrұQ.? HN%\I@}-5MsѱUe6R}rQys-S)֜{1HN=eQ2qCAxg`2G1uĖ//ȠԡT`j/E&][um|titDX&b[5 I$p<W5r c}üvmcźX:3ENbiơ:+NBpzhޕzV[Xn:{-YbvS{\=e=mbS cpci Q&.}lE|HH R.ca`C1l@&p Z2f'hML[ggHb*`CaѠC/ c଑N}ss&dqC6i Gx3;[;MY$M HZcYLoN[ħ'|5җMJbЇHF 6=tr3{WS}+|*@a\xnal)UҡG@(iGo G=?֟@zQɹ~A?Fzp Gy:*$YטTHW9Tܜ6:?M=0 )C:Hz5i7ߠ,ݛrz'xg]XO.`/9P9A\H({td(1UNdsz_R:? lhh߀`\4qacQ|Pp.8s` Q#A XXCe2 dKhJ#&Ksi;gH@5 t?:d+&m:Q6=7a>ˀ?7q[`Gt>2 h!} $ !"Gz􃱁4#T@Ek {r^[n t`&B*8,,reG>CtƱTkwPKӞlxX6o"6B̑܃cCVK @F+'${$=TQ:B:k,*} *~d%y(~?a>'oF!=@W9X?;2Dssӆ>CȢ1 Z\rWoPЩ Lm8& 4=n%Dt% ,[\ [nɡִ _ }䯏)؏#sRLt@02yh:c{(&m *<:K*r<]6(Cbk]?p ѷ!<!(!J6*C NB%`. w$X0 `97 JnDVZ{l*k|(81 u@rh,B` d|N71U84lT" zc$*h,pXlx!1cd;iM;(H /$!CXS/$$ooӔ5{g\zaB G3۾a;G::qg)hgH!{G9"W ev~ڂ dž}J#K#΁lցsX_c=$t@p'g6(kq¾BH;^:6+!Ȧ#.懞~g0HgMFXxy`./t(S,4C@>$<.z '?=l2)*8/e6 9iag|ݬkI:>㦰եDNGg\alNY[@W6j}o񏕶u/TJ+ ,%cz4ׇBB`J\0rG*D5 pLP@ܢl*aߨO@\D(3Lp0;v )F/);A2& /7}EnNd&2Z%2,"%>[N4\j)Qu͜.8gt43u0Z==^>*Yq͌.hJGU|HGg7Dcg)tLD*N2ΥoGKL흥hMA7`_= x)Uza9.V$m5}ج輓 2 2@.6S:+XS Dk=y@eo!&zܫ܄cD_tL$vaecbߜ᠗OX)HdHGCזrԡc0Y P> /QE̽sٌݳ9' TOdqу)ʚf]-6*Z/! ǀYq:%\" #N>}!e윃IS42{`_|}AKAxu& a dS?Fe}~Ezs(י=?6a<uA,ҍ|ҶC =-;Q[:{l*`4KHR!)i46H/YIS1Bߥ.[ / |#^.SS.25cNjWet/༑yA4:`.%kۿ%ۿK/Ng - ,drcLlO#.BoEMSEc7YϘ&}u7kŠu)alDg>Fs^PEZd˄\BߙTUcWeг|x#yG|d a &ħoAB֯kP?60v(&~<ĉؒDICr׋'*j:vK|?%?0cp:& $-> q!6!93F%__䐝Gb_WBT_8 Gз!o[@c2Iơco>|kwN:jF_DNN @'Юeʚ[r*,7w(4]N%4Ȧsi-Kd>fS^C6%Xҩ\elݣl91@(КGzk ^ȮQvU .mD83Tbbe6/:`S3b=њA)p> 6d |<0Yp X0_gҷτ>b>D@"=L]D3% N,:)1WCsʉc'Q{.$|Mo\I4t-Fd=yN+nRc|$e3ퟗqۦX+WTJH?=RAHGCko X;?b⚦&&ASٚL}rx?+qP=?({$lSARՈc>WglƎʺ*[߯PliꎌLck*΀M ؆-8lH_1,}:'t1ĉ) +06dyS,gA؋>fRgmO`k*u@r M0`AzD)!@;#ǻsᾁ!p8A/.:aFA9#[V).lR6UHbKd L`hqQUBܓی45'sG8p{=jk:}hZqĘ12w,<C#nYKAwx a߀q,f2Uz˜;#c;Wy5YԑLjdzl?BkǮ%VVs{Pޑ^U4LwS&?`\C-8m'"Bq"y&NXc.YyÜ UtdU\"utQ}GNG%O1Fxbf,OxWdp̍@i~r)-]C<0Nݼ!WD)!#>pշuc2pgy<[vI d9 T5*dG҆1_"LEU>|*4fdo]K5MNߥWXYXJ}hG}>tĮ6Z,Ox3%]ob W&܂~ Q|Lcs#6KF bj_wr@FI4\TE)l?别Rp;z답dT 3Jfy \}\Lqc.0}lz" aM>r"P]CFKM^z\H% "y:*Wкa+ʙ fFO𿳐st Z? Ԯ.Kf[:_[[;6LtYk.q>HXh!bvTrg:UrƘ=ڂm}fI}XֱNk1--#i~xѷdMǷe-3= Iؾ#[288{+Ltd%0{&/C속Ħ왩} QtɐJ;<;5s3FTΙ068}$BW8Q>xXL0gن5Tu.=9 GTg5e)p ^^c_ċ,- \p!yh*e gF@YlyL1;_X/W3FGYyQJ7+|sJOk^9jL0>أ˿>}A#Кc{/j:=|tx:ojg}'Չ[t9VXp˶ }#큅9s5YzU$4Oe"=t`x,SĩB\TY)ܮ'rƛ_fSMS񉭓dhh*\&\Ga2|^\RTS*h6 y| 2njtý4v. UxڐJ7(>2nS'1@++RMVBFRRgP7x~l՝%얎d%#@bq$vO;p쾓͎Ka-gRa㨀!5/8p@p&+k/[.y@xbzذȾ#;m;p:;٤I`pr E&<6<>K L@+h.!W tU(~Ið-fTxl \4k19 e.7Q>^C`bj''_؅Efe3Zf"mVڹBuԁ SmPH6a毨܃s7!>XI61qࣣ|lDe3c1+ %!>}x EM3&a4P6$*e &| tR-*S5v . +Hf)m3 Ui3[fR7suVV[W@*_,avtZۺW kjxAA&^c?Bp|&wݐ ytI#*\:eW#珢Ja=nrܵ9D%F%uV6.ȅ!#:ČApc"#6#NLaǃLsհfG<B.ct98? Gq*Al|%5rqmS"d׀:8#|'O(e T)SAV޷`vqc9*Xsk0X'k|W9 Uy'NY&nQS/ MW4#3v @NR2NCl? 5S K%7ҝ #"*Ig$m:䋀>4 k.@Mi4cxZR/xm@8%Ȋq9EEqOQ^qnkL$!_ͺl\uKǷ0Mw0MmI.a#qRK$a/~VFytĦq%d@>#Fl0X=1!~ B?R4)ҽ8>,thGCQ? 2ZpY؟q-H'c}]x”-:d3p wh>j Ȋdd֧J%oJ'm5<+(ko˙sF/87gl+}C^nن/1Iߍe!q^i@>Yvɔl{VQ.gsG8W|ΤFMdGr%W .Fp?b s`ۏG$wϤAG u~4:G|Kv>3Vnt 醙\t|d1Yq ւ.Hz[cCn./KTM଑z &M} ;fD]nfdåDݍoȒ8㝳u BL3NigD3RIʇ8?`Ө&D53D%YyɌ*9lzFz:d3Z5cZd!75TZ˗p-]BL,dEYs_δ-ϖ;Xw&3ZAc!M4ZFly*)q9T2l4Y7N2sQwo}]A x H% {UwMbN2#۾$;p)C9wIQ9(sWU Ǔ ;*Ko(w~_V*{#qW;M4uW-W{_r';]:6SǺ9j7vi3Tޑ/sWјLO)Ccj#¡a-bA>(*1JaϘ@^Q[$fs"lO\O]G5_I5<_qwzk1ܽR{e-엦/IX Zx F3.s\KtdEd 2sҹmdZ't-S6<軲 ' ĖK wւezP^*m}yUԩ7v՗noT+uAt-ݎש`u=S;C(QxPYܦW4ݓ? ]I]h} =q*U~U]z({_|֙Te+_dCb?p&YE\6dSfN/M>ZcZ=N颫24g3O,U#+ZAQig"4oL&nL92ͪSV~啗@^Eq7q$żyN=1֏ NuV"w=9΅<%O}3T'zxjnk;W +Qpr>Ul ~#>}b4>:}@HL1ĎJ!yt`]/,iS>b{+s A~\E=Fj }9E9ThFjgkbGkz <ږ>evqv'أ쩭/V)~z), ~0#Z4?x-dsj{;}W[:\ 1 eʖlw|ZAnzrĞ}-TwH3y[7ȫM^̑*h&S<|̶~٠8F剟HgW+0qEٟlf~=kv-.` yatm6yZ;)}sB,0;7vw*n o{FLSf#Ƃ:>(KgE^=S MG‚mzfmK56[Φߋ.(m:{<o'0*Css|K=>>c߼Vu?ηuZѽ4/ o<#T}Gn{o:w!u?畊g_3;_٪{)rϘi}!+ն v?P$^AWp;ٳ=O^ygy򃊹܏ڃ:NE{O3_8{P~t&/pnnN\3ïvs?3ĥۯ}272{/odۧʆ)\:MG_pܑNj{Nҽi}f:v[u')E}mM{Nr:SݿX(@y$U{Mg߫ 8ҷ}/{ęu58%T(W0bi3Íbӓ|꼗juvֹ:T\%A&n*{=<ޞ̏zoJӻv5U=~X]PdE^6G?\@(6e*r16]S?˨K_\)p|~lSN+9l<Գ64Ⱦ,$$;ѱ|A[?=Zgz&5zonfqF Yw蝝Vާ,EGJ$s7RʳnfW :9PfbǷGKbl_vf\I]<[ׇܟ?>t8:WN=nsg=jEo3m??ȟvW e6%*NJr>]Dݻ'ܫ\+_ϡw{+.hg{_VSo3/ EnG0'NNnEYj&V{ =l:j˝ڻI9Iu]u.U?-~z* Ky[0~BU-&<)lLQQ$~\i[{L%}d*˹Qbۣ%۷>'^'!ܩ'.e~ȲN*ݻkG׶߮]{YP>8Zo )oS?isաĕ;q׻"xz.AYcٝۉUU[7r{ 'OEܫ})ؕHgR}SRÕ][emo/zZIuü@N˛#۪*'V>ʮe~"^Etiq՞~/.Ǥ{ڼm3ٕMK+}54VNwgsKdȎݱ/$?ȫ;p9܍×]I@M(=y%t WƾZ 5nvkG!u^xΐsGٕ}[ W{ʷ=ZxId'e os=tt?םY1:j7_:reӯ$ƲC$ dK$2KbeLQ'Fl9"~~{d?$_*Yt)bNE>U̻v=_pQM-WRH,߁pCŝ*y|hsH+,pߋbO26QjV:|:V2odXTX$(c~ ɢ/fH-c%k(ժDݯ%%3GLLL@WM +ddRQ %&JY,ZJeL70WD\qbk}Jr+1W_YtZLIĊ[Ur_I܋4ZZeMN*%߿K(l -x4$'Z<\kbvo!u ,vlG{|4sd"ɴ_I NG0F2D2H2X2@2}莑Le Ydf-޾HwIKk>M^ٳ6}8S{t|Jb;ߤro^丿Sv?JjYҝJ^,kR~zl7]O,C._Svbyꮮܞ׾??}@Wwcx#]è>F_/̚i `dڔc-ZFtН1@2\2T_O/EhZ #14f%:+c_M*=SZu:bfi̲kieU˷^J)v1IJ3K\/9~>d?ZǗWvVTqƽLǙLyZQy g!qk^lC2{l$#QoPt[ Bi?O \ #0DүsP1#KJ$+RƧX]9OwV]H.t1JRY۵2c^)JWDajccK_+Riώ؎/J,㽢I&߭ ޡ?_C}zHF>y?~s7o Z,z6f?oSU/9>=]tGyh{UUE2 sXu9s.=~1؊%~.{h=^ѳ.hfD5c6{GEmD%"( Q9CUc2d$K(QQ` vm#9mm|Bޚs}wf((bfX{g1-tOG Np{r{ێs-.TrbQ۩G州TŹ _h*k6RZ{{KQ\wm?=R"I~^­젱SfE Wɪ j6JοSx?a5Ǒ76Ux 4f"sSWd:Wut~:7Ok6^ly}-A]s ~sk&x}FAm?Q?fu=(l}{ 2qf_Ͻ:~Xx Wߍ&93 i dy1jg2q)*9l<_i+F;MW;|bю Zkj_G:e.~?(#}ֳگ\*qRQ/\{)vc3險;m[e2MRDTLu2>iHc>Y#h'9ltI{s)-M8@Lh.]*n:ژ;i>}e/#k '߅}9MϞ75gƳ__^-G/_ ?/`ad:~ M1v@fF+${db-F!C^Ltߙ8KÐ}5a2*ػ 7;ry}/(xvt \+ NΎ.o+WLP? Oea MOUȏ㧪9%$ud4n2ҲEƺKd W4~h)ףS]]ߏP y*?[ oQͼkۿȵK؟(mryt},MijďhD|M5f):! ;dfrCBdf-Ff3 M,֧}xEHՙ3$obkM87ki^҄V=] nm~E~W16 YMz?9xu$&NFf6`2Ͳ9+ F|&Ϧi4 MI~HԊ=sJVHyU5х/1ܦn浽i{w?7v@NRn<u`'mX̃7;=[diuf!3yp!s-dj#fa,ehƢX4׻مQ[1u * lIKmu7r$ܗr_}(H%*](ΚͱcEA<3T5CjB9ciiH_ a4M]m)21[ ۤ?4YH@ӝs,*4Dsn$~Dys==r/*isNk9cܖ#mo綿iE~kF+f")2՜i GccS@Z6kM;dnMA!eh 4ӽr)@g"Kh[5Ł;UWUxJ9WNEަ=5?;=/\X5ö[)O:/:ѭl2)\!,3Er,W'8zXc}52U4lm/cU||dkd3F8co퍬l4}I 6oDӽmAU:*gNinf:=u/fqcߤJ3l,%cZ:xkhFjSEZZ`ASN]*GSا#t2LixԠчՖo04[?\4[LēW:/`vF[7sz~x͝W0\)Z/]7?1gsT'4 #2}zd2gqǚd>e4uD3rf,B6nh-p\uiѭKi;TN]ʥϲ_(+аlm틥W/緞P"%D;(/.}(2r㖫Fz3mZ$;/M,SN& sg-@3,P4k͘Ybinh!)+qN)@0hb)프נL;ҺW+?UNqz\2BF}4ف؇a_m} [zsƾ[6o;Yy^?&9='"S?tHίC!ias <M<-28ںUN['s̡ V]~rNǕs](W& W?^- ~鶤'MPAMI]/|~-qfv9G~||6~`3G ь>#ЙhND=Zs\ݶZJ_~J}QYeW=]^Hwy+2.s(s>u~\~ڱ˰r#RTɋ?(5Bǵwy9#Ӑ{~JNYϧXY?إ*f'|x#2Lmhs"ZHT9Z$Cˢ_3\_;w~h"jPm )UVX[0⹑SO'Kq~e܏W}Q7-}_&o~, %p=@z`7r{QKϓ`UeZX{0#pt/ESDh dmfg"[Q)ZBh4gqG VhU2Uj; J7`_"_/Z3$?\yuw>v~#J঒*%sOp_+ {Yks-ᆲfP%w~}ёKFk[]?La_KUp>N#|,-ET ZE1]B9WHIꯣY&e~{wF_"_rߟaK^+RJ+Vn)93JOaK[aaKA7vѿ3o-TFBXqJrzO k2.U? lOܪ(%_f&DqMe"[n9oS](W$<\Yz3O۷਩y;vVW:.mc?)}R l6oPlJ_?)<%" u%I՗di WG?ξ >I ~dn+zwnB?Q:nڣ3}:Rgy31c" ZU8垝zGR22k]ǟ?by`Zj OH8SZ~=ao߽/*y-`Qp^jl᧿zҟ}R f$LR JFt@ג|“4i%[t2: }u%fv$FtRMTqm24^V*U[tFr_ ɯ_eӿ,2Fǽ{^ hMq({}dp}+9J^x_w^AEDATCtC {g7r'l}E~0)QndNĭU᷾dMtt Z|+7;[v)E)}"47WỌ7aJ[oP[a {4ǹVxsȮE'^O~Gm ~\)8t u]@Z5hI<=PH\vIa9SO,ȏn̕Q[nꣳ%9UT'F̷~sWMo, jH4H˞ׂS/>LμgE-7OXvwqgYbGc `dRa:qog0Hn/e}hz@uLA;зڳstj=kd`<]ۚuHHd`U"X)0]^"nT2Ɏ ˩o\أ/9/yrktǒz!?}ocha Ձ7/K}Tv uw$gօ-XMIzVh<pjX/Ot{tV?~WDk肒 اtul2TORz]k:W ~n}=_x6 =A yy`wZP#˩6L9#sϼCʹ.{$`̡_93uJv"}{ZVUs;GQ?OWR2SWe*gq^1J቎!۠xWi̩9Irz/$#ңQ Uu[,G+gG-CN(Hgg?QU;b5O> )yNRoIU [3^ `]d$W:mJek1Y}W3REݣE Y!]c;NުOen3dj̓yc=݊"{~t\+r |J=HKfaHLm7*1.쵐T>%`O:F v_?*c8Cpѿ2 w>hLf$ټc$@ȇ8Xg٠W _<証Zz #b,_{I?/>1p–)42&qLv'fԖi0E{߹P^IOA$x21⽿8K+R}`=s[O~X(<toy/︩fUӻ'SzkN%B4$ABzzЩ%_;U@3tِuEje|'7(鿻4 ߸{l4xnNk LN'C_,~Vzf.s/{F7~t9tN1>Y=IXqhAn#/E;#RЩ0IjhSz4cESjW@Bɣ'qDix/2}ݒ'93  z/,duPMW9ҥgK(QJ ~.w}j)귤3v u]_p׀׉gwNėuP7ۺ ]q|6RvO} 5}p-#4M)跠;9A7􌈞᧴d;wb:GWS8Q\_>)`ӷ&cI3{; Qr:Z|ZN{]2:N,:;}TTl" JT-z^7޾R1˜}#wwJA͕Ht&7 ?[ w_6k7n MT5ŊuGg7u+JguD+4mYj!aFEejq|lh%CCe :P<Ёt j>g)3E~ZDӅQw?i:3z=h\L}{\ϊ?=4 sEC?:Z/ j6">k-Nn7pqjf rrD~0ŔiͶEhZ0W=ӥۏ,<\esxuG"Zh #tk>齲N;Ҷpwć}Kw=_ &rp WSW;x^{Ȇٲs::="-sU0{E8}5cURS%U=(@й*O'mH L(:"o¾#N`ma*SB 3އ8<ӢJt>|ńlSݷV3Ϝ}|_ =߸탯OCI[;GZ|3Sw~X+jb+i5?Q:AT#|D TT'$Mqe8?-@3Ջ"WWW\=qlw.@B$ Wd4 EcPUJ}JqVp}O]gNM%0U{f{JvيAk.} hbHcéownԞ=' !_XgFWf|~&>7Q9Ia}ӄa9B>Q[" IW6fkZbMV/wF}#`D6l^y5wiۭu]:+I(@ o0^}+wΆ>R2MLqjzj;w3[vs)ϦU)$ib :6j>n8)NW)ju[7Bj.Ԗ#֠!R$ Lu*B' < :'=7gs{يc6T`_LR:Bצbǃv$[rZ*҃\*/P~N\qv4p}Iۍe,m΅D_c.|*Im*Nn()(z MgA LsY*iiY&}`OO Pw^,X\W{b!1IJPUI8- u,mQd:0(KXhUZ>Hk0EfԒra -Ea cd5h=6fJWA?^wd<>Wz OCOE=oDpOeI<{-ynf'SuGI|"'IKi TZ!ndz,:L\~t$/Ck}p<%g{ĊpUQht21i@3%la[fKd-V2KK,ӋM }&zݜ\g̕ :ޘj18淙M͸Lǹxil^ђlDgi:DoS݀Z*,M=\tBn/D`T\mE<;j7{7IﳵܖAt .Ctۘ55ʃ|~.aO3>Ļ91>s7$m_,|%i3y5IF]}f߳CPVA8vXZHy H_."{{rG><+'r[f6%W<0 K.α8&g80E]sX= $}u@h\OQc2q5x<:0-'V]#C-f7fϷS_we)_R!37_$UL^/FMT)Im5\mndٞل3OHXL@SJÃX8~-?p_ 0LH$~l"h{zwqm3=\_Тfr}ǜ)q,Lv ͘n6 p}̀InȤqEtbc0pqB88U6[wNKGzVQ-sگ_ x6h %@Å/Lv䑜@ 5hF gÖXt~)hu=ދ!{ hqI:-!ov}ʖne&[{(hhPqZ"9U|lLӔD獧 m92_y Q|񎩠#:@L\0@I(GjxpȏR@ӕNR-g&Z*eh6_10>4hTC5]B";_y=B?qLrc/G5w)#(AWJdЗ<Ig %a/v9]h Dfb/duA(  ށ]vZthE4ٌ:czc8On+BވO.]:T/c1G~ w { -fSd*e!B]-Smj=Rj',acK4Ao2}$2M@ @'0ְ= )qhqa.htḀ Z|ž9WcӘF֮u"Zc :! /,#phC6_cT~b9xӃt\`61i 8>X0I5`j3WCt cO’iW>pH>YVdjCP; }EfizxE)y]cz69u$Jl_ % V瓰LdX0hI%8fC4^UE*6*^t+Apo+K2xנ;w\ 7Po5LRũtǵ0M@s TLe22IX2q)BT%Tkl*;epGbyZhˊ oO ǏJVm1N`àRV~IS?z+`E{pa&@Ƕ1!R=t-ov"sqn+E~%`t@_?$T@&m7 Dr,i\5eQ€Wr֠):; ŭV#L|]nҁ;د:0]}b=gu l|0g'3Ɣ Og7ODlA։m ې~|V9Ѯx"5/0`^ÕNw=p73y{ z s"y.kt=['y}5\& 0$~l 7$+ H7V?b Zw\>k½*dtk~+!{7:,2\>dM_n>yHoHH%lхZ7׀]pT@Ki2I``bw l/W[(5K2&TNf]zzWdYM2WлT3wvՊ/ÈZa,ȣh&cո0vOכ"`!OXxe|{َw#Xj"<_H"l_1-nv\hxrFw|~TDjާq 4m*ЧBpn_^u hKgB@ftl84MHK N's W"Gl;2!ؖS.ؿl=7E/YJp:X+`5#u4. O|d:C8FQlp,xl9\}+w4wAxGg!k+Ks:lVKz(?5zLѾ.tݙ̶ˋ]sӱݖ0|пv'^n U ?xs"}&"^"f%V+ *[ Ɲ,`_ h.7agawC:t_|Wޒ^;kC84Uز.FMFdMJ#̶Ҏi̮JAK5dgߌ9)Je1y:nJ| a[2)['Pc&z:xe^2f*cg)pEZ;KՄ M5ϕM%+`T χS67n:Al!rw16ηFM|Fl\WoK{K iNcOY߾w6q,5xU.%hu6=%~2Y?`gc(ז2C  I&O+ #6Qt p g xL )RCdQ)-n3/-c_Hz!iZ )P5_/)Ѹ&/Xo1yD?Pp0䴚q5^}%K fҰ?7U/+ql>2|Faz+jS'~3_ -?Y鿳R,|XmW*j,ݻ wf`r[ze}Vlt WB$/hg(@†n֔ǖC}+>'mdW!g<|.\b!%WCTȥ sœ'`}̗K'A[j.b3'>X^%ĝdGIq c qp <  L`\䚰X`_c`8|,vY5C%,`|V~|LxU`Ҳsk0'"-o,q (E&}.^Pf5a3&368n }±׊FljwӐ~$U}w8uGp0jp -% bgg݂;qIUPK"25%8g;L&-ʼn>o*C]gWwĖm?gmlx,dΪzh.|aÕarۀ;([k|XceU'u'n8~*!f3`r,ǀY֍/-;0 &bFUԄa]8`-ظRHqn(Fb1 ?bZX'*tK5`aUZHX`ʑ>\\-8O5̇|lT%OT2CC(^/0:g 涡;k0.O̓jyA΍d ΖW}b vO57c*]^%QYZlH4~a|$z$`GWf!l׍\u-{90oqGrsa}!iA90]O]+.o&1%:$&U~r)iFSrȋ=!s˩擋gy4V ye]eq<ٚ6,qݲ Cq߰4[︽m: /ry k=e1l;⫳@ K| xO.=Tro:8\ ^LxɀpA4Ģ1&bVd`11&bi߄1o3PZo.̡=HIEc4Ho93+`~FZ\.a`csğ+s I)st`o|90?X[j u6/0ȀWw­f{Nk\>;_NX<]]!Ԫn!tjy*KW böt'ٞbzJ0$kO`Up^ |cptXI7[J߸ÜX;l6#wLuT;3 |plvgLyުb,Ya9Wt| }}t39n DzVT{|r&C~91t޾߆@_^A^^T6&[ lltYkH 7[#h1VX8wbz`>$tMI2uMҴj#. 2χk&Ro9G<jwC[9l\\m`Ls&- kylmƓ!_ԃfKV:` k7`\~biz !$JCXیq3&qxاp&t&o[g>]V1IMmGfzW& "9>]SI\>lK=3V춦OW˦7C>G ֽtpD[   }NWW~sv&2MC npzpK#~q}|q}>nq}>nq}>nq}>nq}>nm,0`=So@(xSrxެENIɮQQqIVKyZ9Xg'. kk@۹V .^j(<8fCIQQqEqhpL؟= va%sRʦ d{6T\\plxy l|b+'`pj1b34%Va"=[KW-_b ?,[pʱ}Ͽ?&?%Zpo~ma=Fq>Vʖ?͟aʅ[ɭW,/~~Zie`Jr͚ElnɊz\a*B&R~| VUDxU}H$٠"ӄIID$"[S!%9 5˝Ct"Q_"W.D&oPoNPb %K>@'A#%MV ǟ!FnpGk}}pQ*tD&h3'"c*C,)Ёj+U2C}@%.~kPYҳY{d1yBOM4-tc44OMAhCOӆ|&ME\< JC8artBxzi % U}#*`5G$MQcԸmПCGhґID4l-MV$HĖ?>S[]k*+(&4Eȱ% sgVe)-=¤ɵ:#.Θ SX.!Ohp䴚uDԀEoQh5 REA?X&CsmM7W_DjSQڒt  I@K_Tk#g~ M>f̀-ѕ|P$yr >#q`:#OPg"5euyҔu`sӤYu&~]4},g iƠH2*~4-:*MSc >Rйm5[%;e'ϭ1pqZУ@NHA4z3Eb<)]G^i DaS>hP04@te(5&4NLFiIПia>H_E4͡K^ch36iOGƩ!!MǶZf=5GӚM\6>GAzKH_F 雈)۔N#:Z͗V#k/Ӟ`tr|-=^dl[?..[d q)iIo F"b4s.HGȇ;zcD oTu&LVCoT瑿;Qĩ 4_@?O>zoH+Vltxc@/,g^3W{~YZe$K.7&끟":[pͳjH/*ߺkdh5YΙ5/8hMJ:-@GQۚ/IH;,wOϐB 6&=a&]i o|N4poЉ Zb_KG>.X  +ow!Є=i@$:4IC"PCt "ǁooTcաG Al*Y\ߠW6Fk@0-"}REx&p|%j#/А&Nd1RÉ,ЦAKt%/!iR]nւGYy5A⣱}IAsȬD,m;TMzuede3q~` E-dUomgH؈ |Ԧq*\9IҀϨ"D  f7Al腔a[>[Sh6/` &c \UGކsˠ Ѡo >ȡM^|]i;[ܲs&h=̒gw[B>_0gO(=zCZJILh2sYzOžt |6audz@9S `6Q8.lZ&0Y &أ_dQ@/$lh؂-vw' Ұ֚ VPQE7bIHw#!epswon`-9;y1@.l#y ykCd}do7' Oل)殃gX.N?S_ZqB G]lەŐ 0&GAS ˠb8M1^:YN43h!F@? =A ,$<<g `N{)/Oy^Yc3zN(Nz? 0$? ||G|5lg`QCpE4Ȁ< *;2F`\bYzMBЫ|:l4in9-i>gf΢;8trYW/QG"w"TՃaw{BDccUa~qJB4q|z/2ḃP-7F)9_XV5|@c?}c}4K@q >akXOgfA9$o%I>a+ “L q2)涣v|4V@}#%hv' !ZT1>f3D4dBNBxO->Ρq˒Ɩg'}Pe!΁j}̓#)T =0(>óU(aK\Fo?)c r\7 85a1"߻+a~AmMY$wr?Cpbp\N"Y ]E!^>h5ziqpC!9c -Ak" }cte\uGc8+g x,<,s sĵF c-%h Y(.`K3pE5 c&s#<3Ytq4&EĽ:~.wڇx a a-n.G/d|6Gm֠ Og(>!}Y kS\ / G ?ml0^P'XUt{&j; E],&z2AX̅qTLSy TjwSI{mX4ćSLa:EX @8\ӵp͂]XXP'n_zKY `-! 3~DC|dcD9hS /^F<]+])(S`/O:7m&/7 +87AoH-|kB|~kp2+kw;b5 !61AE>YO_oRY4AC/ i4m0ٹ炧BݒG΍"sX8ĚX -ehbmOrMz?&lC-caMHyB%;ߐ4cJ€ՐmLȫuL`iQQX#mȅQnI 袭S]F[NkiĽ]ssNc.j:袁x%٠&[ʃR{0t9KdV~i1`"gחJ$!6`CٛmLR2vֵ]̈X/AвWq|Xk4ϭeX-%[:9Gcr_ŮiJ0PA X[U݁7syQyk7<S ڶc;I~18]<1". H_1P endstream endobj 65 0 obj <>stream =Aob :px>s\bʡ>hf T4X?q#+3 ,Υ;F@\ G?ǿ+|Grx4XzODsayLv.cBB SsNcMDŽ!g%חvamG8<ݳbmF'bց .)~V1jH?;ina ro37W0QJ} nhP){%O93{ć/0V)1Q3hh΃$`(Dc@`@xS1qEb?t]dF*_ּp. ?=& 7ڕ0/i~f`bj h=[O2ak姆z]KCFc<xASt/K5V}C{+XSj=!7{Psj5QZmDŽ@z^ !g zLyL="PH:b;9|v$/$} <;ƿ7;;x)*tJ*j[yMtyZL'b ̉ apjv,jT:hÀa~DȋGz@&!9@'+Z,F|okXZ_Q̈́~!aO/Px@509 ༠ f ni9cKw` ğa1/:#>K;}([DŽDŽ_=&,ID0ޅ5aTز]ltN3朶Qb[˘m*u@"k sN oÜ/0t`ͳˡ\/xhIkWKG:^K<19MuBUM4" x=p'\ =,>~F#1z|AGFKõS4-bTp&p:^ΠD&Wa;DHj?=&Ώ`kA_|{ h>QaMC x(H <9\|1dֶ=!WA<z/vI]R`<^S^I A#Yx=s@xYp+SdWɕYًzmt%~9+5<&1QWѭ-j35+M*ŰCu%8j=<>/ :i2SV:wT˄!7V ÞnZq0T;aJ56LN)W3^nDys)а +\\]J=]sa][ csX ٘lB pVXʞo%`u$wkL%7ZtUM"m:n %x*(FUlj:')ͪf>@G>ys铗֥~y9g8_zl(I-b WG^CđD7k)ǯHV|P@XX')>pMKx\d6;ڪ~]W8)$=a5 lW,raT엷J⒱քb_7X4W r[O½cDVG_[, y ns C5 k?C6~}iB:&N(c|)De_k􄄱%K6)(a `JO[@@m| z; xo%iI'7*[E>wV<a^X Wsc^ W.8ЁOJOu},y8#sK_{ջ' / /:%bN뛹Ix,N%UG哗l_}U'a9#Xo> %?;O#y}?B>RӵB91-eB🦬S3{ahۨV#`Lj9(QzaE=#rpDu+ =1i5W K4ܧ@fG<хkXG9xqS A*ݓr58E8R4Qx k9p8/9J\# xBfXM?UAF2ޣp}<1=*_z&is;pb_/{  / =`s\yi4<hPKY@[^^{B5S{Z>C8ty Σk!c:G"w:fQ qW~M5+'BT88gcjڅZz7؅m S2J*ѹ|0盱v`ɬV52WJ z=dBa /qnV {~ozea}yqtv v|+ =EX.\d8{F̄^K:C~n_ Q~^]k?s'DZ=s {Q&0>>p-CϘ<8B~=L؛M]L":}(Yqk_vE c*.2%'rpP.Ocv]u ]Aj@ZMfҮ9ȲMO}k_?Wߌ~IPgouOH^[_7fvM6.IKA_]E <$<]& zMiR^L+D*H2(X[O:&{ J QM/J ^Bs"lfe?0i}Lr>X7pKkQc4y/Slu?u-LR FęJKEff<_YK'hٻd}TZ>:(@٨ dr8G U;!N ;B_l4s=O+oIf`ߟc:Oa1›@+Sknn76*tv=ӸYb%OkOHjHrjI: Mx+^FVCP_xM0U;|K2ɭoS>w-ߩ9Lg|ՠCփZP״|zmFdÊB 8DV]x z\񧙴K<|3oa\{,ݔ.f.x Όnb/yʹ~r+?X}'I+ĕ_,~pR3iF-O_.I?4Gg+d5Ԧ"bdr%kh▰P2; Fln; ٧EO_uPTրذKѥ^w!ywUш3`|aHtE,ɫ=dA ޡ!HMwCb ybҥ!hӆZavĦj-%Q)d2j5f}lIʰfAW&3? i7͏ẓ~)zQ¾pTuPt ]vJ ?h9>hwxc'O>LnB>*Y˪M^`iɝ*)sEƆoZ GԋanM`Q.8xĄMjW<%odf5<-a-ԸZBtX+I(Z֢-g3{YG#,ي}I~yyZEޡ-J{E񳿨 wҿeP6,H^3wBK@aY ŚN}s p/x@}罿!ؤ,߫Y_m;׺Ҽڣ^? ZQ2^ު:l|F |NPg!2:5ۭ^Iٷ=ly9]y,|@u3P"{O " 1 V®f/w A g̩N[ua*j]Les9¥P_ Yûw5anDRTqы: &jo:MUӻm(yPKJޭ>.-*1.+v=$NVHaʕ^Mi4slz„[W j ~5$KѽşKv ?5_>@Y3-i21G;oʨ]}Gߏ5y)(h:/i4g_d}G H^4Y;Ip?H1OwM&%A{Ss*IiIueѻ+: ћ: q~Ye9ESRKqa%yF;bk7nC3!ҾQwG@͔_ Y1G{`Suy Yek@YOZGEm}ԁD͍_V\(Vi4ޯ63Sm|wn1Ar" Ĺffdo%Y5k}hP uB6r}zI}U9ZV v=;\L;O_4ុQƃaOE!fDG.u[]fђ-Aq{;7XIU%PK8`t]/LԞ< [RͻHT? ?yu)%x[̴Q8Q1׿sP>Xņod{U"ߵV8^ Inf*RoRW?j_2,sU: 0dOQ .ZË||2HJ܏pY,Px,BSbg]`G]`brkW]`ěo?o6ҵ14Ż':-:921FS( ?Y;J٥D>xХgg'%/-$E5Ow[hzcavyjɉ?r%٢jҩɒ? ^$Ȫ7x4 Һ^S%C=/dL{4Sa1qF!^WtV*|[q֤߱q:Goh՜nUTYy!":bpqmXa]XiuHUuH&><1"5*7֢1-ƸUh߬q$Y%1ǣgD; ~4dRv̽o󂤷9Hvׅ?U܄;a&ULk|{RTP{R[_b&ҭ689'|K֕C]db=bv߉;}3ڦ5"&Cv!&:Uuh_o_g>"ͻ*WYz,MU#TlP JWr{"q^Aɽn3qQYw@ӎћ&KAѧ&n5]򋙤3?չ&2մ8Rv6?a qHfՔrfIo1ʅ&}% !zreͲĄ:ؘ:D֬dIXxظ:pܩrxe}ا%N;NeQ;rD[^Қ‚S:kѻFkqE=̍Ȓ(e~~.աI%1t?SPo|lWtz̡> WRRhOqtnN5'hn ۩!Ձ95A9m73̻r [aEyL'8^VEܨqHp[kQh}N kQ?NsM;VGTėGe{DWEE[6$GmH>ޒ)Pl4R)D)P'|!u~=l\u9+LKv‘wb>:ic7M:H-YHQ}ݑ{ W/tm<_&ict F.]WcCcjd7#~uqƺ8N?'7ș}o՞jO'VNռ>IqH\bkRS{ncMTJTwbf[mSl*@w؉\%g3#mbnqnC9Y"ܬ0ڼ;7BKR_Kmxtg Ίp󞼘SqZbccbZT7;^g_ܡ;S'8gV ~ZkUwLZ[${7""N5gDw^`<؆̫OQ?6u:Qx%;"W7u|92G.TcáU>yYPrViM`Š(FΣ?FGgVg:uSv)RFFfd@E.3$jS|)b0C>7F@ίk8^m1Y鮂N[%/rES;~ Xip"BIa1b1CE$"1PA,DfD5 Wljܧ* +Kbkt̫T.F7;G'Zp*:8QU،C0?>!~b԰)DbD b-^ T79'ZN^*,p|- ŎaAzAl>,r923D{V&?V-s^ݴGP?]h|#5MG7}/W?>aƢo$TfVpG^Ҏ gZsdjccd1^!,b0[CG.Q%Nѝ%3X>$;gةoNLA#QyF-!.'j\"VORPJ^n~R"{9[E/ݢ_Ft .eV"Uy9"CYiⶪ{ݚjn+nVb$cY1'e ]e ^2ANAYB̘0 ϭ./o?i1>G럄>QMXL@PHl'ۓ̙nWvgno \ RJDzF:VDDV{dt{[!eΪe1 9˘X89eLbqhG,$fq3m31Wa31k^bub"Cb">1k!8G@!['։?w qKOԣBHd1%gF] b9F9G;Ekq"XnѦN hr$bLbĬ)93KHeO 71g"f.% {xn 9M>Y OJy5vO()pK@5>ucRJZ؁:R?8ɢ-F11ڤqzozw 0r1gbԵ(7 Ao1knBqnb.7!. ם"Vhݕ=E%[۪;r/`g/Dc7nʛ9 { IbѦҽb Wb 1_نnKnG,E3vMRy̭0 Ε$G=}X3gz~ 8CpoUS|l! )mnɬYZ>c-z Gl Q"risqi܈+ (/&֘Jk͉N8#[rۼ-TkT>%xy&-8U`ԭ\e Jß:D Tgb\^㐘Taŭ0W.chCsm:Y. 8i+yj(u((!~YϠOYM(V%hM;ќ3 $6#%qΫ,Jnj%'͛\ ai-Վ ]IɟzS:+,Roǡ1] BWJ u櫡\L(Dc63T~ch٪{n gn˝`?yiv>+PSMٱc^Ğn`$ʥ."6&4#@Gmеm$ 6O&q180#! 2GEb8>|>|Ohߌ=׸O'qv'miCp)ң{*q+qF3/^}6GNXpBٳe*j9ő_ԜM߉1?k9' cQsS Wk4- Uܖ}Ul=%NOlF qڈ=.eNS:w#n#'t~Lx?/_uz8၀yQ CSw4=v ol?DO> s{%G5ga~1g".?=L:z_xĂU&Ģm'6'v1bfXZX\XUHl%m?hPSӮz9Sw'ao/}|aʱ~[ܽZ:Y34(h^%g*h869k\>6~)}y8qý:$6ģ2(Z-ipBF\kC3t-&J,dDl6(4 9S^7g)^&[o|BNuvB[C(a#֤B ˘+>6E蜷CG7`Le}֤o.e)Zѐ3aGl#0֤-Bbqiwᯜ'N#u_Α8=L0uq' 9V9w0}ǝy=>?rּvnΟ[ }sx u[Y~L2j2Vn(XR^loP |e& g+1E똼fƴe<Рc6$ XRXgCl;ys6o*nO^r{x>WV5p{㤂?L^DA]|;L]5KZټ1iChA0|͆ivDxZנ15[Nq3Y_~iKزYƤS0C,iY]-| uG'~X*L^ARXcA춸9j=nF(8Vr&zS-]0,i"wx3ה=l586 ZNuaJb#ڭ&2VdɰWkwjS ]!JNU٧t#ۏf}4{ꉜQLRw}<@5q K { cnpS7cŸ)wf!4-ǚ:8py6<'ft:ҁW /8O%C3R ̌nM)0s70BpKv K'{hAVľ.y rURTgR& S UԸí|G}S?,p9M|X}+ F!W3 n;A/Vv3 1Xyi Դ{ 6W yMɓefKK%5Vk\i18m>/f5_$fLP"F| U~C}F{1F9UCFGѽ9Ad%2i)NhlF+B~Vkǹe/2)4F(osز-dPr22QzYV.πg0耛˨}t|n*ZAG5IN-P m.7wU[~|& c\k2&~.zvZ#rZr&$4G7fzlX)C)}\/!/YE]ߧJ]pj~iAKRTR.+|`BuȜϚiHENW+MӾIquﺼ m"y~ m1ڸ>K h*^0Hjiء{[uq5 Xsntur:/95x?Q;{}n #w 83},:Ƅ&uBm2o.B`a7֮/i4қUc RIQBBza<\2Q"ầ* Ypm ug"i<] ϟHR6@(I"ɭ?fk{IAu)z[www ^ZN2JAUoFzUSup;9S4nzZMz uF(+B_rIw!jسM9 !4U՛ =hg44 -u}o@"Y&nM=g$nasGj靪 Ǖ_U%ȋg "o9. <3s}!0rM+0~jF׾*ŕ7ت\e9ʿIcYβivPyQl&CiۄS lAl[س~+ui4<Zz@ ݫ ;-f"]63Xǔ9}<ʷH0yȂNQQg75~Ge/hRv>yD%c./aTR}X׃gSחsSܧЦ-/%M64O-xfv#&RTwPFx7Yե F5[.0]t0Z"Hd j ?GD;Ed<ó^|<등ẙ;noٔt$M٧<[M<_A^Pu m7>kwz;(sLC C ?2:ʬ($!{pww!"USq!n 44nq]YM3FQ RI5m+ep1ܕ2P-Dށf#u;=5sFL`uJǙ=[]4{l"b/g .Nl%(ŠgcTt qyrl( KCP/4rЧwN>^m_t~X]j9lU!K?𷽸\+|j8/Kps`Tw8ڃa^^Ir{_o^n6HOs^7Wv mrϜ{0/3"vNkcc,y&ӿ_͛{G/~߆p~i+7E{"Jf~f$yf 1x_\uoWB4&,q= jiֺ4kXK.(ZgLܵ{f;6l!_ i+$[5đQ6R@dSoSUW:Or=" BrX9vdƧ }xxa}Ao,Kz; YX{?[~Q78ջ\]|={Ynfۆ=-5fF۬[\3vf8Ͳ15Y7kR{i%f G/Zl->Xeh $-B0( Ӥc wrt&] W> VV֫/,Jgs'Ͻ. }/~*G-\ ]DnUиxA>4w$W; (>8{|G9a,B{Vs+?{er6\\(! ,<>QHm2K/ /v g~)\I9 }m[˵~ۅ9 Բ8-QƏk{O)δ*.-ٙnfrpe>]iߩKjqYpf9VjZqqqѸԸg>SwwxD[S=mh-BO9-=\9NAypf7k'T O?4vŲRNThbg+מ/U^_r؇0f\C9n _10W:/>ou>m՝E˼{~]YށVr>P%S ˰޼zfwkonvNݺ>$ = /5МtZZvb{f۲]j5:^SAǤjC,$$+>qTqc4}jLj IQ@sCH1wzw+g5OˆpOwݏw/1*U^=ArN\Y{E)~K,+4N.-џ.]RX.ёmםO/;J'7svl\NOt:'=:c9i~ F4do  Czn H1vra J ݫa2MH/^?^L?> qRiB:iLfx,1y iI6Nm;)kX; ysШNƜ4kwV1|@3OEIٝӰ^tGw}O?w._mw}f5w}ЫŗzW᭗n7g步НC7i-w׃ܹ}Ꙃ>8̞ۑCpm6c~zЎԩljǡ7f;Z.`>AF/#́6T7]ˉRrx\c]DHo6 z T\"ս:$Y%-m+t>۫Ŗ7w 'I}_ϟSQgƯ>.SkO\!dv g~9Y\zNNٻظS%t[ b\nT}yE]w꫁ F/ cP9r Қ&BS\%> =AF8¾^<_u8Cیk~Zt`-qr ,βBdBh(֌2MrY}X3VHuQ"{`pq !e.ԏ|+3.xH]3UT꜖O7/>`3AcHL, TW:ЌF4*,K73`NOٍ;}[fa&?3JoE,:7]Vˠ&H,e5'C]d睚.BO4|d h%τ8t\;0O(ĔyHb3=MW?Z2"g%-Yzr\7_R/,^MLg.|VσnGt幠~u/Jr*Q?1zV?ߨy݅ڰ{5ۖnҸN7c Gm}(+b }[?p!;hE[ }1n<8qpЕåWAJ\{k \h ,lxgX7G 2C'8Zw(ܑR{hr ctQgۉ%;ЛLmI:x@N21֘=c#-!LHI崲qeZrٙЫc1٤50ZwfTbxBC j㝵ȻK/>qy~_e+7I'{)>"B:1Dj'$.0g>~a椳319Z_ꔉrN0a#3l'Y`L !#tR8Ij$ cuN&_&ͻO=Xn{!ybC'/1:?eaBR]ӅUQ#v@y&ޝ5d+!ծ{{\J(f4K[s+9zPqn!| tc7J6~!Uvaa,ۯ|!\/W|;L[j\=pڐt}nZ "qX fԱm 6'a ]LU2&N-8fcc8[G"ref9x(ƊT/.Ubh5tĖ7UgB7ƕ| -b=͠JӠ\xo< S?O7a Mk n*4SԆBd>0 )1KobM,n'H֏'AW`qzWO%LAtphեc1-Br8bvPK-"=)9{ﻪGb5K=gx.4I2ʓ}Sleשa<2ΏBsBb3_zr6 ŕ'Q#qa6>J9bEZ$YyI٣ٸ`f HP%q΋(5';(.Ńb}sgb*>=O -óm9~@WZo{MZqឡm"q}3BR;>Z{f]4۶`Oȓ 1Z`B׳]kZo"Poo$ZB#i:ǵPI9>:R|; YޕDڲ$ z W~ᵧGHMF/%6PHtBK/GYfnt2??kqF/Aqz[0kE'+'*| \Vhگ,ʺBKSz3Nz-CnALv_N~!*}ܙ_"=q|-\['C_/pv{ x1l1w>8=3@Y\lؼ+Wh=/vmv}  X3Bח;}_mN(z"qKN:^0t}&|^ƴIR+{ګCoߡG`/f0[Kˡu'KܫˋF\.tj]V%64Nf?t$avCב1*/4T^]e,X %TUb(-SĎ;ש鵓H zG8~ggq'X^vo@T񚝅!s!~4PY'f@,BXxc冏7]}+-ww;~l 0|H!6{_X{rt+o۟EC]y7iqu5 Qz>JZܨ)d5hQ:pXn_b/:gy ^ZG$YD?5[==%"Wf5¶tR9|^c372z)̇77e*=r|#4.P`׫ZvctԱ8ɡ_048\o|}ycg눋9 |ǫ|ǫ+ ;H,xpnp[wke_wkPJ1|,sا:g>{_{]o/Z~fys|!8 qYcJA6Xwj|hAhBY<ܧ|81`g.$Z-",b9 х\;0NHSPDHS<`@$cWX!qLq &VNvSE9;R6;jOC>{/KJ͸xkLwÆO>wk^hbgM`g R?=y\*"Vm 6<̈́3l5;KmEzBvָV43kQjH|Z$dHA#7>v_{:1Ќ-` /!ĪSKƛ·{+GNHw_t;ꋀ䨃OĪsoC,>ޑUW [^6(Z2 Opq 9'Q 6ש6XkpWt`&z<ϕUĮkvVY,ɵWNLf!ŽYbD]^ 4 w",m^+DGsa3Aӛt^OZl8ot|3ꦪGO.?$ABG^ȑ%d)N5iqҙ:xCшfc3 (5:j!?@PJ;˨$i8]w/ qHL5,+} i용Zg?@etlە:46̀!Bv_;zG^kz}f hVþhf%c GvuoIuS?t׺kwwCw6^RT3ۧ+}!IFH Վfyr̼x?3=8j_nB+wOEۏL ~fZavbI#ŞT4ViaH3ڰ^Z">sE{C9w?tj`gF1Өܫe%:ncMQF#&>uvT5yjxO vmw6C [U3?DmQ9\8mE8{^)jGLͤITBslՈl;!6Val[;CH8: $#_{G/5; VI{+ǃWvxɨobgYabL &ZcB_=R>א9SAG;[PR2rcƬ;)/Zd7~(/yh'>H,⷟^~H,-j;kQ6rf@Ge?|;o'Rey;r^sg@ $`Jlr{_K?x}եS%&y*JdBFd4O}V̷{o|>O?6[?B!-{ M8YNeשyb{Ow-mza8CL>hܣZvecOIEĀKcX-צK͟mC&Zė4bg=!|3Ȩ; IBGfgJU疫y(:Ig* 3]\v5 gŰ71v:Ǯ19]Gܘ6'|/bWUٵSP%[K-77_B̭ci_7yĺ7u8e=s!v95k*86:1\hI/4S{!31 Y֐^;dRÙ#Bm;d3?, B3dṳqvduΧ_j j߆F\B۫!0L|;ŭ++V)\hP<:ߐ K? Ĺa_3:!bU6#Wľ ࢀ#__I LŢrۗ`R_>z KKu`s@v͒ƞ>i})|g2z*_;K93R)g:9ZL,BϪ`Y>`gY֋-oB~+k2f aur%ݳ增Ě/6X/xoqm_<֘0N.::wgU_] 8=WRVr٥igȋ|;j+Ow?ڂjl8jx!|ߞ{ĒI\g7̶ #A_r0fZ݅O\483&g?9We+BF fKJ {TL>r0Bħ[Qz-2+Q'YMs.'^Pj%1P ) PmV!' n']{MeLw{24ԋ_bσݒo^+kE yXd(' ;Kl궼r7~ZY/&N+M)>gIY6oc]-k=3aT C]9P\lf6,Գ<?pC.@ )F*V<ܩ{:n;칫WйɔG:럄_p`}nV9=Y3ؽd]8&dT-%af.y}Nn~`z1\eN5Bp\‰?G_Փ#tXw)s%bUX|=#`ќeыC"TZ)V]^^>beb-QR|~!QhLYଡY2:Y=LE%jPrpFjc-i -0T\^{~x\3ѻ{3B˵CmCqVJR|i8 |%J;}uvjBbyr>Y+/B%40c7p-.i1ЎbR%ȥ±dj(p=!;cogN4*,+ f#ŪDe- Cx8{& umb{kb8멞Z_G:v"rV\1^%xO8@q+8ç޴^X|!( g2 H;b'τ1!8i|kƙxoP*,F{|bb P|rxk3J1?ԒK 8!Ff*)X6qR>:K`?-OLFMYA^^݅G[ f{Q\V{l Ex {U"|o>O`J5=Ϝ:pbWO2G:{^#]$|c?=;>7YC{:,vv3[> 8;ql)Gns\ǔ;%C+j ps 'ײ84IfVeռiWtm齍ا2030eN\i&CN41ω=t 0*Fd؀)F(3$ys0C#Ԕ*TfT|蹥IVs xSJLxAI'21|fz|i/GiG6A< Y)*Նliv͔Ι8#GeتoGARQ;#$nzB#9 >fܢGV6XMZMWLX5.^7#O#X賳5$k'ȋ`Z+49 9'~(O\2JYN> do,m1nGO-xeاK/: :m5Wy3pD0q^rvm 93).U^\NLp]sN57ɍgùϷ`=c+$ pow/)4҃>PI|?vbZ`F8u}myyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy?oT~z_Z#Aq}W:ŻćFGǥ8mă87-N]wVEno;-c߻jŚuN+ANL?ՉW\hHh.?"[~MkWXzz׬]nƍNM+֮ڸ_/7!/9l/GovZIW99ǚkhD_wwa߸Ig%(14 hAIWV96鏷իVldpZa Woy䴎՛V╫ϧw[i 6ڋn=5nA]bAo`v+lquj=VLm1G}p>8Zhƚuܶ_*i]Ը8hzk m8l :bcEÊlR<^4}[ Fh-=||witcj5>R9pD?)ԌN!r$931JQWȵ3R11 -0jqHPhHDBF,/X&FL[`*a6J $J'ANIA:bm*t0ıߟPI!&ӎRu)f`9"H V)ذg l-'i8 )f,ik>$$d %g`\:N>d&b?}T[5zZvj!gF}P3! K0f{zKFY3RVT!Af_'ԍBrF, *d"ɌS8IkсS1ʄt\R gyDoC9d!l7V5\a4A 9ǃA 5)zyvMC s K!*r%CV|Fo NiHXX f>/AXQUbd(mD1}HAD:DKc)'[a9vQJB߬:,%n"e\IfO3v;AT9$gR6#E$IjZTv}):1]mYΐU5#1ۑm0R@$4&o$hMbͰ7%C$9@2B?bAB@BgۊqY0Ni<>cr|iCA9ǦaJM/Gd XCFq!QbP$dfc12o(;Ttle'Fg19"И^JDs(Tsgձ7-waQ2IEtv/=q̛3j V10koQ|3Qf#Rb8)$Fg,F\;B èir)#B;cST G52N O(1jQTkJ54* ʳcKh vH'b_CKl@)i\J:bJ `"Ӌ 5^CCFsFd4|2]X*WbxHf Ic1J-OM(K!b_2V˱"Y,*1 'cE qeKa=1 ΥYQrNtѸsq#} j"M _wGd6f`TMά$+*^y#G0<iQ7B]4H8EfyF^fg>Hv $yYfEZbKͅ4k!4ݐ~Vˆ5E\1Z.$Vdu yW9BeS#̈́r~ T{IB5< J\hFHa6ITKbl%3dƞARHHK?cfMM?>ŔtR`$ҾjeMjEERGHa1V4r 9i#c唺 !2H f4alF ̖0B~Hd{=6L>RKhZca| cNgH*'}~61r*b/gjyf67dt=]0$9@>F!{ȁbzJ$!]4[Oe  I7{ q= f@,,.eB^VZCV x<jŀ $g!':Ff1ţH iT6d+!d*ŗ[@|)5鋼߄1 dNO~V!$> 2Ƹit?"@52ێDs7L>2,4O'{4 w{Aޤȅ )B>*d!Id<.n"I2%QƑ (qf05yl `C~*ѐ5[t.1u Ia@~E!9x$yjffSm1Zv~d!A%_ZYlH` gH%Fs{f`̔'A:\1I1_ήq Q|r.$X0~Oɳx!@JGL̑B}H}[cӨ=$â !3'k'| cQ,^@/<4C͕X H|rBwVp%4] NDl}]tYॅPR'*uWWb4]alCFde ,~j_#K%yVa>B58%ȃho$&hc&l}l ˅;|W\\K>Kc,~f %f^ht_QgA#k,,62USjW3QJ($OHB ļ$W@/x4“s /W2sTc)k0~,Ʊ56MnҠj\)>2x4rk0Id5RBhh$+J ⾹>br/jG4ײK$z HxkE@FX~L'/Vbfd`SƋS3;Yst bl=Zc9rRz&3269vqd,V >  l;9z', ߢ 9,,Sl%i-XARnO\N Mzg\7zos9_u~!7$Ȱ{v Q9e&Xa75 FɇNA-RG#l})))`1V e5IB8[A)#cu躷*zI _Z)!a搷pBLfRvi b3$`$O|?C?ܒ ZR<8R6 M-d .7%X7!D~ ިו( /!)\7o$I2RhHH20-`yZ@"pO˪u=&??: )L 6NYHnBH U\`z8cFoR=U OL11d[jZ83@@kjVkr6$R@)!#, U&7L _b%6 ͓jIp4IY'_:ʇxI$S<5ɷ==5.Nk)P[+-u<9AB#dv i@*|4@k|h!*k I>>dqCKB i$ZIil.oĶ?!Zi>pH>j|3d_3 Jt$uHsJ ZcXc>}KNVYR8_hdȑQwX=9*HQ4YO2G@j\H0_/uj` c  d߄%@!-zOƜYE!j, " $p^/vT-N/7m> y,zjZDCjIV1n ˥s{ }T>L(n .>^-njfi^< .4cDWn>\'e4OF~(fTb+sa^_"YjK|Gn d!+Qt$+@)s=K+clpkˀ]ЛufWXS`u?Iȇ%Ddhm2G)Afu dѷCb3 5$ A$%4Pn-T_F0daqc}̥,9%0k$lC GWfvߙQ?Y-;o@rĂ1H$>4v>ȝ;/_w u`j9FkEXOP~ܦGOϱE_R2z} 襰|!Boܐb*ǴMrC,e~jR[}Cm@``.ɓ$Q5B>bZx)/IGǢ*& 8m/-Θ'M\_䞜= Tqm.؃Mb~LE9m*wI}jG>ż:a(ۥ6J^ɮ,]XdH> mG W \o/黟o%I,[Pu:$˛-oOTJZ ׂA\X-IJ1cd>RrɅB;+䲫KwJfLk1Ik&)[! j, $3X/JH }vP2xh.Q$7lSFw$tspʕF~}}G , 8bN>R㈸e8YIϱ72;$ +*wφ+IqWNK9(c-Xu{Xxo$:C.jT7ggG=|5g+.O[>(־RD.óA<:d {g#?BGwOi2b٥BLbzJN͖sN9!NS-b:d̀;jX]b6jQHv+O~HӺfqMl` H۴IgEnm^y욢^'D8[i$\w}Rubu\H+";k%jVz{:Sj%H$tj4ZOCi.'U\Yb_CJ9czDHajhV!'?I~RYX]nd IC}NO{m3 *UZIl;U-c`kpmh!}G2!~N8m;b lH&Yw ,TX"Gj"< Ȅ/VYT~} p#Q&Wnc >P7͒0)ԍYd-Dz|)@bDY=J4M,ؚ$Y@#c`_/j @TbmJō`Lhb+™krvi]z|s!!V !weP>턾dp"fwۤrGg}HBȦ^ye(_@rɵL2É#)`k ?hf. ?ClL(ǙVHSX3aϓ+K !dđFBt?ܣ>rқ'FBd3CWM@~$H8B:01($mgMܮY ;Gv]?T*.{gO(u*%c W%?04,#q55Pl@]A}$!iZ?ln.dhxY#SH6eSϭ0Mb\{v\:RʃAg{ǽ?&w#u#t g5ػQ.:kz+_*e*./rR,?9؇@\B<6.NQ,`e7zQ/r5>ªPveh`>3G0JǗ{y3^/Кp^ $zz̑ywJ04]3>PbXgzzȍ/M{<)胣bh}Wilfg; ;.,1GϞ]R[7`:󅋡<ȝ`!nd!UMKƂliu/5ڔꙧ<73L}3#5cJ]BHԪqTGU.BK-hEHBB9`H2Vu;~թ$)]z,Zۡ=!u4CV#)7ދ60P{gg 1sQk^}#Uct̀rݝ./UX|gq^ʴ3"!0WI犕$#-ÙeH)_[jퟍ=!俄a%WؽY1穱8FIhduyJ퍵Z<0VV^%fخZN}uM_^j(M;ZMH#qo._}TK=5MTc% fSzSjprbhh.!u0 =:1О` ;f_zBF`[.Ďt| -_j3H ǫ=\8qk=bhx>rUiT;*\ vX'&1-<|ń~b "LRљ8#|4rf4j3>BB8\4rl<0ҎM%XaLfyCf"zObpf@)xd_*g5|NA :W9@烰߀}CIVاУYˉΕ߀=8X\=NAgdXS^l 틍ѳcp&„#bOY7 Abz7B*Ȅ 8pCٙ%tqvk|'y׃rѕt,eYͰˇfu[둿>*jJ'&IgY~\iĞFqXHIg[cBl=ݨX.;$|y;bst=",(JSY<$9x:/Q@^=[K-=ꍪK ]sY+gQCZ{ث'tؿDg|/C}!2K{au,ϐKƒ" {Ͱ~: { :jq~b,`ܹ;p,;glUFLKO]yO;Wϔ5~n# vekQ|4BKnBYRAYõYlmms+.^1? Ƽ΋L.=Ke}^4pG8:a'7}r|X> Jkw"%cY'W|ʬOO(gԲ)S6=N +'Q͵Ya3і-|-3梲lTk`ā)rguN|k1Vכ똏E^~YFDp.Et MAL3H7WM6K*#YE9G'b8YS[&r cBީY\ݵ;Ͻɿo=$\MN̓ ?u!Ī?^'5շs12} :ʮl.Y%u?c<*-$=#vGo޹[`'k]8;"]_«d?}W*[pX_#pgɷ~ nYMUo}aUGZCý'.l}ko.;_R^ Zcj",&on=JӕgŕnJ͟Jɼv2S6QBI!] keTZ6^2D߅y,}V gFH7ߋ$$ E9}j!h .YAʶ21EP"yT6Q{CQ"O[ES/}rJT!n0_eoaIz)EGڹ#o8Lpy.֌C){+M vYw,p }Ë_3Bş,Ȫ^IW% QmQ/$rG }O| vԃb6 n EvaWNa!d'UG$qהmB"Z-]XGf`{qŠ(jC9PUݧd͇3dm)K؄?w?Bn;Mi&;=mfC1⑖pa;o`lώdsy ɀ15a&w/y)h]taD 'Ir.pw=7#$x)L.H[>b ,-q1,,g^T*~pLrM$}o*WPF8Gep)y}EHDڀ5nǯ}4BؚvrH Z̤ ,ȇ]b)^v؊޴h:.~i'z +n=0R/;m55nzoi[%ޒ# R;d32Q4\$ U68ͩΓT{y߻X Gn8g%mNMrlL?՚.?ڙo6\I ~9 ZRy]]g|8@ RW oCKl/vxwV]E2 ݯ6Waw =FSl1u [EE&+W]ζfgu8̻6y W\ ` YdnsvH0ց]mJһ xBv<eYMƓ~o!UK[x/+߀=$~?<8.RT?Q1t|6l!yrLAIŎNPME[# }{~+yi7%DI ?ڿcx&/]nV6ޕEe}/Ɏ~78Y$̟ėxdcѽ)]Z}ʼu]$tK=ۖ*ևĹ7GĆ^nt-jtlu(;]\]Y[Y up_{'.2HВh6*R4bX5qR! oDݖtGUGCwted-z>Kğ~_`PE@q~tXyh %qnq 'N5g$iϕIFX=I[#%Z$_7bcVw-'D{nʡ&_u!|Euߊ|(3}5m9D|w=}WgTt1| y㒦HIodZ8ړ'j'ޔʜ1ǎ#1dh!4aL2L0gouOFl׶w7 :q'գ>.#b*_Vy6nn޻j}$-Z.yŦ(+ֹ9 grPuU_qmoac{ZfHSpwCL걮D Dw }gumD$v|[P%ʉ~k lHp6"+c0- aư? !z>i8U =Wo7unxGc7p. hwN׹ 3@pgvrW`. mx4_{|7q-e Ue!w3o9&θ39`*7F?{g ,^uρ>uuz}Sي_'W|.zg4xf׺TVzK>Y<B0UY4"0~*΁d^ Ʈ&l3VO lљ{B|aמqw^H*-W{˫TWxKozzǽeg_T{~{5{[(3X:dn[B~r ?}98F&3q+ubqx-<9E`b\ `=[}o?l 26Vw.8Tg< x/o %5C FԆ `$K[c2M3{DT0c,0g20kjU`upTu`~msWK`{0l+wF!R:Ԕrg/o|8WuUz&9#3-]'%}۶?TgU%X!y4`PLm31k;OMh0{9,8!` Vi`ŽN?Pl"9G ~[s:ض`,Hwոˋz%Oy5y+"UXܭW>ͰmE^1O*c?ԹD}jtMjwMwOM5N9/ 5εip g4@}viC4,7Чp@}&P_ 73 (ml;u٬Y}mkޥ7L|sih u)= iM韛j2kc M%״׭`k\,QSm6fY(BS8́͘x&[Z,a y_Q]󶀹wœ %"\X  9z ;25-c`RoF7$X܋o͕g(O{}I~-/Ţ,λ16|`KTr o2>m۞llY1dz(Ar<̅Qc!6i,9ϕE NwYp)1+mnـީ&:h̾꩚UMO)'$baĩdPMrGv-V[YV7\P}is*WC>6ˑ}X}0wzxXw] hw6n̆s7xPX5n[HmAy\4zDG:Ff FFϨw1"60cQ΂ڕ sgD.&Z4$hkIaɊ}]*ctu38w0PiJ{C!hYs`B>X,q ,u`Y~X+ꀕ+u4؎*\4yhp̎*jn uuGcIJZ5WD犏GO/!r799Fan9M%gkJt ) RCgvX3_'ƨFT1q9ckfÌis[P>Oֵ_0ٷUP 1-8󻘣l.4n>G(Ab&,Z,vN~XRUf&~cA$cjvao7H0n%Cnڃd`ʘy@u"XGoV &.`DZ;.VO{k=p>q_2g3n%۪]~,yA s.78N{2Q\I0tokλP0߀/Kge޵Ǯ0:OZ1M;;="6{EU~L\^d;_xӭ_C?`}4zh<;~ъl7U3n]TNjk 3sx1Z̃q6g };5]ጊ}QkɎL6yx ~\ozCFp27b,=?}ObwM3fLSb9I1HnC^Ƒnu78 2FO\oꜤj|*n^S`ѬlVbK0E Ƶ[BS7[9Vo+#Gl1"}G uNo6ثdqqc݋?,Χ04wR@t;?r+?xɥ'}ĢX.qgP L&LIg-{r ʒt{1rzbmM@߷MSȮ )`ps~aFg8Pƽ k`Mnm9S90sEˣ|2 8|cl{f]mj:B< o6gB&ןDKX9B,Y:[%_l&2{7(vXu;_ yX0.U:pù*GG{0Y籚~uȰ/ǫ>:fr9K{1)i޽``9@,g{ch/:Ļ.;qM/|_ 7~[.tQŢ.ork4[CGr9~>{cQEhwJ5\}Ҭ {>L7C-fln4?CK쥸QC <{XlVЌhP-`fun靉242oih<^쭥ܬƭܼ݂0̙c66p>82Q_0^y@,PJ- ߤ9M͂7[ć[)aW{]=^Wy)y>֓5C^g4yILþt (~Ϙ2q<`b hm;ɣ^!盻9f&})_\L^ .XA^BnhS'*wSn " <[Hn؋G=Y/Q[ׇJ۪BLo]S1G\{2BQ[?#g ̠ip.+$%[UNoLԃ o=AOWb!W $`5`- VJ֎g<,OzMAҏͱXg=~n~ᅬyXHf7]W+MصEq~M4uX5v eq}lG^λ09?v_vkV7n!c #aL cRƈ12-c( 8B^8VJ' ֎(D:"H_oyF =se-x'|' Ӆ{}DW,;~Bɋc=bۋx֒j_tԕ=$d"/&ƨ C3|lʋ 0{uOqT8YL{6G_Xqsf &ڵ\ǒ׺8t~ nHړi =>E#4(p2"r4k3&a.DxjDA{Vi!Vɒ:dH$wKfk>Eg~gu"<\#(flZfR s%fbpbѼW.IGrN7EHFa4nzԢO(q^-`.DR+%S}3ZB{$sG*آ ﳶ#ae0PV sJP 򇆤 Y0@{]Χإ1ߔٸO\aP^FV^mk_&Un^SskZ7iH{\W] ?;ՄS he+ہ1w $NAjŎ<߀6{4UN7[D`w?ޒ|jeY }|veXxM 2; o| L{4K>6t{7t.v\Hy BxUI8`h50MT=ĈrBsLrT9yq ۊ_'!$gʩ7ܯbѯ ]f`w9CxH'>A[tH+tP?b1E߬yKy1)bbL1-czA/!o%OD͇>;2 =x{1W*8|~  9)Ysdrf#*hÅׇt."fvUiFqSﴹH5\Tl0a AZNkC{CPq}HG8<.Oc)M{=}v^I\KF4%v -76^ݘ_<1\.t58vcSSpوzvwWOL0Gh 7lDyNO 3a< 짏 /\U!2ڵF\Q+K)[[.ka/W *2vf^_vW' *\%4nG0O {ÎAHaJ.vru]ho%ftaD0^o*L,B/x MD׈_5i31Ss94yCZ(߁%X`CTКWlrZ[;pϬ٦Z{޵=.#@l(u)eA2^TpiT㵈郸RCcQ֊Cc>Yɤ=G:jOd4hh+zm% Y v_;ɿY 4H[&r-Ŝ%WL,jv>Xw4l8LŜbU̡x\Vaeg,{459`@w1@:HK 06,#*$9FT.$c 9}u<8}|/j[>85 imHxu'IY,S<\3q˒tpvd#bF#< ڇUî>iۣﯢC&b @OyAEQ[psHڧ 3Qo-Glw) c ğ k,[4! :N \$"A97o`M qe1Yh]MBپXr.;R s^ ;Y]pYC=A؈ԙ)ňMIe/}9:; Yeq%ف'FZ= $ [ME=݌:2 (҆BTEպDMN~C=~oFջJ;B{GL8w^\#G,=qC&=ZdNaޙAK (BfmXnd`rV}4}2t p^61呮CNz}A0!zDǯ  h"ش/b!+O^Dx!|a }&ADL#f`ް>rJ8Fg}''&0#]\ꀊ+V,{H@7}>Uy/./fB!2b7Z;FNXP^mF : 6G{МA 3 Ϝ@&Cy v ps 5 ɐtu1Q{%'("(Q1O|85wVpxV@g06fBGq"{GXOH8\(uT3 ibb!+nqJdH}u RAAkXVJX܋Mn=F;X)[ Xk^!.nvtU\*B{D|DPt ”}g+Ghk5; Ӊ''#=b Ee#VrS׆fIEYvQtKCtqn^Q*FĥCL%Lj Q0'||M,A {"1O7Qjb둁78jqeN_)g's VoE>SxI@!'Xt#XB08Mt (' 5eDB:M]_xu0idd]YXwHeF9MCCf5G,S}]ԝvS>vJ c0f'0+W4%o$lH[(Ycd=!-giQd9P@9 G /u +zBnc:>s(lThuQN]Y H^n'25X]uHi5->kٍ)ˆ}QVgtI OJ[@_vDp1}.q*l2Z]gXhZ0|s*]y09Ȏ -q3*"nyZvF\| 6qe$7iɵve'/uя:'P]elAd3*<>@h_=Ҁrg/:e0 ݢ`L9qcʈUBBL(Qe1G))8< =.Ec0&-TX^Uxqحu˅4HEX? ='O]Z̪Fb^ sStE$}\wDtēJw9 0ؗH71S i}xYA9ưмQ QBt$9OC_zl,wRCwoVoO>>nAt%1m/M&ŨindyRh3:FW|'J%eO6#ၾ3GH,IWhzPZPIb!Maj.,.bNwўsйԅрﱦ㊘( '.kmc6kv z[ʨF,f7r LX>Hsp熢LLqlƾ\sһb)|ҝbu&BM11Ж2OM΍,ΪLy⽒WZt Ȯ 3*F&" >͵w{;z?}ECpڄk4d䊎S8:qɋ+o@ډKOW1+J[(j0q3#c MB,nmNɋyD PH1GY x~ 4Q[1qNF,nQ%Vy^)ʮ5~u7t##&"?yo:F (7Gk先 YG!=3 .ٯI76~k]ݤ-Lݍj^uE%Ffk}m CZ3Dzav> Ek"E|"}$ـp~^q)*hޣ5'qd&"FSS3˩'Di:xR.JEYXw%ՅD=Ftqٵ ]?wr6⻭fxdz{SQS0Pk k@Υ+ "uVFÿr$f ;>yHX_򎑶 JB 7J,ΆL9Og udbHu+]4Ψ?4YL5yig< ^r@L$u^G|ɹhu@z$C!2 =11z:Я)+7'qD؞PAq::iwVm,w3ޓ0cqY,2Dy |#s> 3`X9*#mY!uLIHUDG& ( ?b摹MFh}rBYR"=H }P۟vLJβq#uO/zU꛿\ ?3rMj(OC:o<җA_Ud7aB^tPLCb"CF5 󣩳(#,fHG:j'si׌(!m?(aeVdV)`+q)^&Z"]"(yҖA:\#!0gXAHv"Fb"#>/(>Il\H9ٜMۺA>iqԞމ$OEջ}K%܁4~X>`UajQĭuYͶĕ:]t Z+E,n1\Qu('Ek";qcu#6"jzf#^YPf`dc_YuZtV>ZKK; [H5dϴk4W^2S;vǔQ|:OGa(B>W83;/%u}L;h+ѬS y[ϣ:^1Y>k^6pm-l>#gV ĕA]:8H;i㱟[E,~Ji%"2hmM"lPֳ91@dT&K'.L4YcsR̐,B:.gMQܹps%Z;``/XTb [DF&-Fwx̥6"]joUjI=ds%Gƛ;2su}pJIN{GEuۣ3fQ[m1ZnsUub $(J9gs9+Q(َ_x{n o{<(s^{kNP(V(WjgQCSyvh9 ȅv nMSIOvVhv򟵳\jgZk9[JB;>`,L-0(2?3FC[=;;Zr6 R3P}™M Ky+&I^A>G/h~~9r_ȊN \ VZ[66#9cڪ)$C5 N˷=O<5z6 c 6m~ii򥸒m$\^T-}[v8ҝC;vG:2GLH5\fs6J#C,K&Y 4I/z$ArCjg|R:8 q{B(]>Y{c 4i- sh.S//<=JgAT`$//1vrK1|N `0Y,jY(0b2_aS[nvKJ%ģ4)gW)cÔv{0Q7Qqts\YDuc"Zz\g)cϐSF={>R*;SUvN!T^R'ĖNX,}s62m;q15۪YT:G @~M d>L!AZv{BE| ,8 (Hר.j$o4KKOdS.[)r.6<(3GR};y?H $Wݚ‰CuH NMoT<K?kb a_豀f074C8yBeXWv!h,47 1u9+.}aTؒpl&3n'`b䉩\娉em!W/ff5sIy'P TWpL8TH#qhT(D#F&I􋦻|rj҉FIyVŕ֭-v ֯FMPwT :oaai=៴k i;FH=7x\VX ֻ`|3OZib-Ny<^4FK.}+_D (.4heQ윴)&%Z>b4gGteǦR} MqYCmdBEXa.͐M2XoxnlI)SatQoWh] e ݃u{>VwmqOp)5Fc.Gك՝>3+|g)r*4-|&bphh`q*[]7^G~4&?,%z8?;eX]"]{}<\#75_A5UH\>/{XR \74Fy%|NfM[h!E6.ؓZ8t8˝fݚY#kkP3"#$v5fz񇧣5H`F=t"1 禌<0U̸19?)t+}8Dp}E/XqR?/+yDm8ȳ:r_hA//l[ $GX;/ZO'XJ'f+JWKЉZ=;=A{bL# 1~riO‡?(5O;`;du獦뀨G`_n= Q$D$Җ)wj!n048g#?췊,Gӵ2\QϼRӳ=y vyz?јc|^=Eʑiwȳo-B/*W1|2Շ !$*M(PN|љIs DW~Ψ➄BhQchL4 {}ic3\G#7AV,ûmhL{&}<yƅ+B>=[Rމػyz虭8{ {<ıa4C;CTOf[h}bR&ϧ:9ΧG>%>\A+uv2=ޅ;# pl@ ٌ+1lޕ%ByrTO FurϨt`"賉 Gz-:Wu:߸aȓ]MCt?pI Dhb _) q)l N)@4b"LE¹g09zmp9زsՆNف?N44(LeD!q`:t@ϛsߎoǷv|;ߎoǷv|;ߎoǷv|;ߎoǷv|;ߎoǷv|;>&O^c:]V:&ɫ7/ ?Yec3bj]wٻr[8-Mben1bt<zKYHo޴7#3vA4r~3{p%zK-7gEK Y`R='<N:uX::MX?}Gk=vn7EGklwr٦G#O-3Oo5yp{2_oy7=zyzz38׿pޜ%?tOn;q`d[z5_\|=nu6mzHt6O~.X`a(O^׶z܆/zȧޥEzy:'.[4ɒ:澛6fC14ߦaia(l4[iZkjl2 }1m0 NO_OmM$7+617QHi$sܠ1V260 w3Qn4٩mj%w+sklm`foe@hen&gS JW3ƔDC6obI_ck(lzE0dLA&^J@ܴigg`4΢h &4z* 2'߾ ͣI'q_JA}DWQ%]\ݳ`jG;|! [a]QhV4䢵&@nsF$y 7IwOF!GO;% 5Yj [58 8w+%bNWCdZ{h4^{Td%A+{`JP8V }zDv%S^XަZ ]S2Qt;%L "CF|PQX8j)xKM?(4AicS[ 3+ 9.x'JTiEd !԰IƠ1\t֒QX{J~u[4΂++ Rrc[12H=4s;Zi ̚ƌ!wz ;se&'LU6m^ aK 0<@dntz),RasDT]l%)z F+K@Ҡܓ0 ē+vwGaEndח)G5<~g4KPҕBJM76o6J槣t2J63d0X V^ޡM /r(faWֿ%? f23qߗݾ'h}ݎ6,DOb# @{dxrrOЏs ;G"B2tiSXئT2zeAȭ^M>u o$cA6U"∲ Bu;&!qG aOo$^2nI+;3l47'>rcƒLpՒ d,wih$0F\8j%ñC漵\ŀH"Zn[@/4ΑA.1o0e4(A44cӇv!}̕Z nZt  :>hBn&8GޢCx?e.?jCt'y WI^}dN _2mHd(g0iW4UR8G 梉Mh}hQFL>@shDI*ʧ(ԟ@?O&>M@+pKƊsz،rO`4j4̈́O@mLeҮ{ދ~a}iS,m`7Y~`2Q> ]ݍ'ƷqR`B*&a[`!(o h#wd͊hx % &w Kq(rX4 #D _U"c {D#OopvhJ1Ō9#InQ12=I kwmV*y7ˬ6=m`,cLmOO>{ ·),=Č/ڰJ9~ s /N "%Cܕ8md䒓 rK $R #$G 7DsfK)x"`[#O2+0g4(h{Rh*b m'&fEȽ> $)zd),&)4"! l]iC^h|↋; ^ApB @ҀxFC4e*C- dPR;J G *\nh3qh4Ccg( P`X7L˹1HxI VFVNVKh\#6Txgӽ#0W޻E"ׂBnJo;%< xs* Gp$.8q.kջ'@ ~Q.k) .XGE5?%8WFS`6#?HSȓ(.mRJp%aٺh7gII,ݴJnqr-hPNJ΁h,t]#=I, 0(g gސcku@B}w(H7Ɛ番Kɦ<IcRb'bQ4A#|,(\b-!Rhi& DC" eDJT=Γ x!hSfpmm64B\;9 5A%gfQ4YE-m %R Lrr/} -C>"L =-4NRp'6;a%sBuYk@ 2Lj?$ Cs ?qg/ȁ4>ڢI[nFs,1htv9w2~v18?B* =+'SAϠA#xMnA"1hmJ j{'rf$K+U)iF/-hB6Faڸ}b@vG|LO*D% 2n?g$ y C_4 "O lh9 p2z-Jb$f*M`RPLEO/`RR; Q" 'OIFI*ZW޻d|OC$@rqaW؊א5 HB$f7F"r<gI|H|2_OӥM8D0%t"yO_EH lW$ r5w|@[qD<1(ILȶI 9#G1 >4orYhs=W*@b,oOrXG2lz*H<`ߙ0h7\EaZp!΍@iۑ| MfrJJKtޯ7nIF8-d= V?C@ S4W 6H=ABm-Ar䕢 ,P"Nג `gw ħ+P("&sSž6JxJIk\#_Sܝ: -X'zJԅܢ(8#m+(./KI  xmD";b6%sAB$.l``e^ȟ!=vkCd2Vw  )f$CA2t\Avo9ɫMINcbn 0<m'kAb/ݵ@:rBwOCt%|'zG|QFFAbhWd#bJ${!w[A0y BhvlYܮEmA { .уD-jOq A&z/ɍIJnHQ/GMTDE F=[Dc+{{U\C+s\_F1?b-{~ f?Xʧ^>d}{PyLQ3%xX@hο#ĜeU/  5AMHJp䥔dKw ۫G1yqgfIp2`|!y?%wxCDI'ѹIux[@p|+>=Ӂ JG:ւk eԄAHA8朵&oGp$n q%prgՁ]ZK)&*AL0AzeW@JP^%COyeؾx-N%A H'f0ܞA 8=q'CO N>7bOw/ >L*..{E7q~q%ȽIC$yQy ծf V(~wR"10ez|<@D@Jp~˲-H[]s' nB +q?R^i< c ,̹ }rݱh\ d Qǧ)gf{t9[^= &&Fڝ ^V@-jd2cv>}&%_z _GHk#0f8Ȧlߧ5f/V|ׁyT"_d; aEzRH8Oi$0(iKrfA4o#H!r>y䁴/#_SŃxo阫@:K`?5214_":/ҺsI4_sqQ&ԙEdvXD8"*^m[F.=F endstream endobj 66 0 obj <>stream -9Br&u%?a8 _B@ɟ@ązIŸӥ1"דh2[#QCD><>a#~@ EɱOӚ+%Nj98E^XB:CZ&= ?JA7R=<[y57M/Ws$ĸ>b+:r~%F=ɇe6Y<5! 0's%s'ap)!5|O#k_Lծ/Ρ 䞱~?^f^pj&omL.d$W7WCBkWFJtHEEPMk3X;mҼDwB KD~O ( Db]eLTҵrZ2| %LM7dAOх/KKZ?ClM:EE^h޽{o?Qw C\{`ob%kŲ'Yߜ_.d]ZfPM| bab$Q b7*/l{Bp8TP#) ?2ڝKK^bK:Vi7zG@žW0Oy`?aJ;$r{crjA 犲'(;Z )k3F[Lk a~ Fݴe-7`8,j)х 1fXS\$f^A>*.Z rBlL`r }mkeGm&sj1ľ!vaDx<qI צb,@z *B5On;W[Wm+щS~b +_DcpYחgc f]XMy-*v>ZF.HF ' E#CD]t]H,vU£'ki4)Ś0ln|eAf<\#wzŮAO3^szcfڍ捦1@C;'+A bv԰VrIs 6nɓ\.Too/Hlwat{9@H['/b3H93 6#؅]THfN{OTVb/pg/JCړ2cEx$*+C v;LHT\D)Rd5r. Z 0͢6zL(]g][U>4jt?ZNGkv_.8*vgFU|IXQl%S߼B"yIG(CSNbrdvywl&$vн=c؂+ڵ|IZ*/jlX!qtGEڝy\ԙ)lYGş1M.`:ͱ!I|N nr'Ky,}{&? v'V#xi&]0H"w5sk)S dL_F- E 1*cOQELθد Z!BkvP Pb|9sCBΦ5H3^U6Ypf.ttcⱴ:qZﮈt+zਭ(׋9U?u$~`ȽQ™t:~jN5 W뷩.V;B`t/[u#a}yѭ8%$t=Y#-Ar6T]sHnJptI!׾bXuOؙ<"\T;q(ͣ*%口bbb%b+=H>RGHJɒ|izƚ  اH}c!Wj=ݤtA8 }ϭY%6odS}5!a-9:ji$G6(.pQfvj6ΫhHk"_;Q_T8$liZػ>PT3wT#GutOŰ1kG[=?$. ;;S\>kBT`kX_!cb9Z>GɣMS.."[_Pj.Gꨛ 7qd5:%\؅t](pّO-`9)1\F2n)<{f_] Js؊)\ÕRan?د}[L`߳>L:P?"HMV$c!ĕT#aDO=9hr3u#h* ?<B a ᨵ`/ 9CkfQf`EԞ䂝&Q1J f3yG=PMztU5p&M_@UH<t.2& 1? XGٺGTXjث }Db L:EȐxÇ g?o4DPUJqf`/qJ`fz}_{vCQu娙\vĘ_Q=g!Ar] C*3S΁Z}M!{si'?ZzgǞn%3"(}o>wb"byֵnq,#Sӳ t=<>p]܉>6YsPP8=0pYtd3̇@ &y_:*rJݾ#{օO`h{b͏qc:y{[`bDtY(#Ox܁`zBr"l .:t=^Eq~^9[QK>:gfRQ'V$"H)|1l-֬VOE1P%:XZš)t-5,g`G!fhʹyTtsNkשi#'捃=WR_^ֲ!K@?31?AV@L۝:^+]ԶğKVB)퓉_]DN"MGΆaaE- 7۫}V /ϧui27_6Ź Ga O|+D-Š'Ҽ&$'cĚbw=`$}IqggwP >>>3)B]KBd/ݓ: § ،  wɴ&5!;fQ2'%fs.V. u~1\Ӹk !دȽ f#~:c}x:ŴЀR4Wtl7*㵨ʊZW`MM3<~PfVtnR6v|bvK5?~$?^q`b{j&vn.B@Pf9S1XVC;ӔZ(\ť=Z=\ڽg0ηp$yh"[ھF,o1;7 9珽6.)oQEUξP{W=%S浪V5ֲUi{%^H (y g /CQdDhI=Wn=Wܾ&x@֕,NK[~TӺ4|a.zu,!;bΠT_t1|#Of.e.\(kYVl+[-7sD./[;]+J_}hD/eo?_.cwBr'Yjǝ~ GɃVy?"{.4[_qGs~0e%{<XFyAV,|5r%ȧ_JqH֣؋EUD,܌uj ɟFDz#N0{6TzWՊn5f't>o;pT5աRmCp-{^~/IWT:[|b*o0SׯW=ĕ#G",*i_g }irEЂ JQ;#XULұv| QAtܑv?&_l/=ۊ^(?lP6(+?Œ?qd '?Ofd8?eO6ϷVIya嵪:Kn +{_&U9.<%ljP\B ئ@BuV6(J[VeĚJyś5?cym(?a'CO܅*[:yOJƛW[cO+*\V\Ğ|e eg~3_o}7KT]gynKv(Oԩ7qn|E4|I)T7l=Te5ejX }KuUM6x[:_&zur+sنѷrEef,o3"~DXő'*lً7񿽋> >T';k_џ_ٗMH[ڗNw<<6\E%C xznյǮ36M'ꭸJ~5_oo7Hמ.>/P~G/T['>}jz}?B :gzIV)V7x7YO-;el{{-۱hkkYGcayYŹ+,>J> 5dvoH;SA%/W>÷!vɂ3)q{)VOt\q#[otj+̶4Է`XVS <2^:ںFs"{Z4V"ZfS O7|v+Nuv'DyM_4:W)x(>6hy24)oΩMV> <((sð,֒l%[g2ۏ(%K[5}6;۵EQFYS}n԰Ky^^/eϑ]S=|B[S1fkLYv糅.w*ϝ߷scL%>3=OW20ks[EniMhNAmX^BCLAXCr֒Bקs֦I'?Oy.sgsIYV.?v[X:s3 ]NsO)j[T<)ڟ(|(]lQ[ .YF\yfs>ʕOö\`J:Q|i:cwsaaĢ𧉅!OKmU]VCRKcӛB+ԕ Gj3fFe%* ͊KQ}l\%=n~kp(}Uy굵tQUeDUf7Y凵wktFyytZ+F$Ɨ=QZ=n=wyaRف_7pߊBKsGsqEo_x+]U[cʶ3Y>-YOss/Vw< 8֒X4X}?u8]짾V{*0ģ@unuxË⧶EKI}pYzK|%6K.W][tIﻔ].kߺM^Q!@ö.iKfT]Mj|Rv0IfIfMta\Reg]?{+> QmJvo*/ (-8r', <⳯פ[wɶ}}&gwkQiB9__GBg35鶝gs+v4F>/>)^i.#SmIi|p檮f!Y]SV&qO}~::!ù<öpC8Ƈنq;#ʼn'I'?8s7$!K{gۼ"1t׸Kշ*zTeO'O|]>mn23Ѭ}M9[?gm9Ϧ2g&e&N"0a1>L_H~ c31s2?[iuDZZ6ڵ.oWSķabKg"ќļ{eՇ_̬u*P}LWo J0 qhVfmd^kĮ׮{ngSGI_Z6П$o2S1fOc[in8V1lqz fӛh3=?mr]ZW$?iLg-wS_KP^TߎUgߎ~{':k}3O ͼz;X}vpƅ!'<^>}?(^_zL]B>[GKc!{ڲsn|OjvKP]F?Y80FM#v8?έ9+Mz~3`Iazi &_0=53#b,Ƭ5@Ǯ@lw mr+og޹^q/,Ah&1&Z89yn`ƉAܪLŸm __z$ey]1 N7= a←|hЫ 0v} 'sqN̪ak;&R.ɺJ},φ\Y97n,Klc{7: xFx;AtK/l x!}'g7ydG]2>p_rt{Mf&L0bld~u&Ǿ e}vp[9~}vH7nd,LîMYe+ ʌ%_}2576xuc]§!Ψ V{;jA|_j}]uw/C}ɿtv{nZ;h3c##]e]^6mRfP;ti*lZNp32 Z< *hx}^p6ϬwB Gf|R_d s uwϗ1I?#1q"fT3}fTSEX_lmF]:v-"{C - *r'$̒;ꀚL_e0sut{C-zZ4_~ח7Xc$3\k,3PCPF?=c3@k"3,f&f`fګow$EMbBfpMj:õ$r51,!a|wխ-GH՚eG͆S~W2# _|%~/0OuW5=9`!ݰ̨?0#/et,fc 7gtf&-,ZwkqGmD}f'Fdn/}u{6TW"ZSӵdS0^#=FQD'Ss4%=1C{b 뿀9f3~~|bTDŽvY 7seVH;5wC j>{np? 0 g=PmZ=c|}gԏ 2#'0ߑq=t3Fw3j뙱MQ͙QL3ofL̏m:u͕^^ LIqݘ weT Fq6/=|nP[~ں1Ys$_doz}`K@fHfTįs#f&V2SLXdK"3z9EΌg MwsͲOuֿhƺdkMibGWoT- 1>G9js7~x_(T]ʉMWcH1/;auNv0s3dfT̈!soM2fԐ̈+aC6f%3fLL׏em;,UgZbفX`+si$?:0sӀF{Ձ$M[37mZ:~gC4G1CHDP2i#ύc1HC{MdEm;j &X2c20|>3]Q̷"/WVwgVyS~7==nbCɵ{uĸꪀꀜ5ez^׏~? jfD䚾#8a⒝h _ XX|za晾K1a?R6$?]Í)OoB=ԮW||yMO?vQ [}6>cr'2!}F0#`Nc{%3lfbT#3-Obn٧}gf5Ypl^-y5ϬU?Wp+}ݶYq0}Ò N vo4hd;CfƽmYp^H\tqBP2>+4tɟXEّ #K4c>hɇ2G{0C&)+\3dfjL\6ܷ]cpX~üw >0?fLM l0⛶˪tȕg sH~CwOJ5޵;o~:IYmJoS5?@n Ҝ5T:c|N^)x䪭57C0!f35qrc&xf3U44ff3LLL/3Zg,̹f?z5>46r?ZF͏+տ[s|wwpӠT|nT 㷉ғWFhk[4?|Y+Yf2L͠Rޭ\1۹AUxwj_  *_ : _2x}2/+jU!"p?( U6 _)}y5-Ov~y-Pfbxk<ڬM^LaKmft5sKf i0 _ "}5hW<5,re#U7UrdS ͚wKKlSzeHf#O˽ O_U6]zz0>F뉫ti&wHmB蔓~cԹ{E6GɌւ6MHEr~qd9xɆVHKWC/?"_2p [Cn78-xr옃[gG Wzi꓎=?z >5Ƶ]/9u]u-/'oBw+Ŧ.bݟfO/꾽Uig/eIb?LRGcLcfd3.qM]^r?F1,&I}ѰԻNKC/<ŧ_~up'ŷOJ_^5(7Q$2UI3Wo9+zR.=y<6GaòL; 5.^z/]x.]{fFоxp{vw!*Sy5L\F?H̘6eJ .[yA!!k]_J_ޕ3`3l&0),ko͓Evf=mk fU_vસd z凿y})r|m:8/e8} $_ lX94/u5ٛе?0OfFMqg%1KYawԗF 2#\Y`x U#q!ak;&[?_*՞b\ g,cps õ\q\zZ8l>{':B2KVSl0G|=TJ\{Jt}"G"}};__TU[{b_c5}{4tꚋlx:<݌I1}/^)B~|aw,Uw۾[|j m,W=3S3cBF#x3LlXu6GupM:`rF ehFG|:f̲)snL6$".*p`XrQ}|c|QwKG/վW.fpkղTm{9m?g9C< !yb[n[OUy˓]W2x*VmaC_ ZwGIqc@by`xIPXphf d s1 M@_Dj !wȵw< !bbu`1RbP:L(2%PwዸWѲGǪsgG|Q}sog c&;IʫE> K<Hgqg>ky ˧c,;/p /\άXar-/|SW6R3IW2O̷A/OZڇ[r9x>u-Npr Ot{ }t+ZsNwa獯R%b;\:7OUE\)lӇS'.ɩ4~m%s07`q%f˗+/ e70K]?oøNsfբ=%+@F>$4%6s\hYdJGQyz*قݣ\M7fs[kp]y*{-^C̭;8+1_1>x<{Ёޜv~` O\AtkO}|0Eі],OZ^Umf4 PʑF2.'1Kggɼc ,=]F@ع3|4'ұg|ZtzP}4H*!m'v0{4wC4ȝQ:z=?N'H^$W^ĩM]o+rŻFݭ雇k [PxjȊWH6lt८;7!Pl 5cC2MjcEndd|O_ߟMlfbX>^᣹rmo+uw7#wuߺ­CŲBn<:g{=a7h|(~HY}tDE֞>LcO.OkӍFl;՚[>Ani7q_q_ SP 1t&T'&2Ό' }B-[rr5zunk>sg탹MV}ʗ .6Θ#92L{칎g\GLˁϮ(Vv wM_<tוAZ92VUvA}XaqRà7`.vURs?*t 0F۟  {ʳxK~{1;ySbf@>s>g=}o]xʞx. _HWNQɟ{Q .*@ 5R$_|Vkz?4*>祎f LM~d]v^Lrug}ϗBGAHY |܎]\ptPk < ,VuɋH+E\>u\5OW>/RߟnUG!ߑ`̯i@laPJ>筟X}l"WhY ŝ$%B^Tb ~⡯o=(oȫ@o`;.aOW> Բ-|Fn骮Y| Dv[7nfrG'pg7]v-v^܉cq3ų0+Wd|$7Hb|Sx*ex d zZOÍ9=\1Տzyt% n.J7N<4Pw+VuZ%|jN='k)?xp~BOzo!a_I㺳pu|e߼Tu'u7);_u~H]qp2"L)'FʰLS.V:|lf y+ohD :0I~S2讁C^3A zEM~80J(4D(ms`۟.7|4]i"xbF+hN?ͦ\gMfGƩw;]p[%'wx*}BXe 6b Tj:$ =Uk;G''q[̇}KQBJm?>ږ/)vbw<^H{>|!z+I ֝!|_(_~?]"'7k/UvSQ6gKGy}? N1ÚBaL9--7:]ÃBLW,cL[Ȭ\MRyF 7f#Im֚OʵTEB2^"2f{40}ug4 Fg ζHƺ, ڮb`=lWu=^&tw=ET{`x|7jSzk7աWg4W$h?]%|P{*Vur ǧ3jc u9ꌦ\Ffr~ugv|a0u剱l涁nn gs?Oh}'=j}1e~N!..M~u\*grm|b&y=?x|\*n!4KGAkms A LRf\thp0ʒn'ZVIgz.:Xx R5G4x{SڏfIA\Q:=s %VRaՇxq{]}v<[*U;8BKw02/cY sI獛é!n4;}w>saXWc k|9BvZKZX S?kl˥ksҾ^2<?RǂM)v&rlkN3U'@~{zL>xq]5Q&Bzu2 [[>Y\,yoy x|(2X{/WFer!K:X+lJ殳p?zqC zyx2 _c-t|Bb\qp86}rQl fȊ}cd~9?z4Ly*ANz|\!,?}{^mm{B++m#gI}!_!m,eo6I}q^o'5 Jr`B7jŬF{roܸxH%;GQ i}1AL` ep}/~=PF3k'!TspX'~ӥ9`@'ub0$ϤYK%p])vnePAH36 P`'ή0gc k"sU:>1Px,bd O7KwG ! X I6t3/? cȵp{LW>4,8Q ~VL˵GRVfSM]_{i>?$&EJݏ}(t⩠(IЗW+V=}c`7}4%53uwKڏ'VuwS2K.`|HMe#%+RMo3bx9S-}[R+[`ϯN }؉b3Sk4W8V%I"B-)>%7 F!+9!΢V%ϭ#K_PH|fO|]z;얡ltY@ĀL5ֈ=A kXB$jQOΩ.# :`q'WGb[/#kX[DQuv=Z} =swIǟqSZ!1&\H\n#$V;n/3+4A"B1D,hƧWSS iuD<< YmL9%Q2Ç%A)aB<+gu2kLr#6"\H DQKS$_4D=Mw=:pNϦ̻Lf'a_Nl^9<>&~yCd݁g\4.,>?|E0/RQ'_ub<;'mʭJ5W΢p\s/HuH 5^h mN>2\LWI L(ZfXS9XzY1̥#Hte&9dFyUN.4ĘDspĖOuoثoXhMnkm7]wܑJuSsFj͑"z}0 8r- fd/WXifʐқ&l R)q̩PYVBHGcyF@޵%=NToe$t+Ο-`CKMb5 MAq$ʓ:grmOJ{ss`2y9⹰qn5{_r]OK:5CSm{'g 1rn>+=|!܊[]lI0梲̹8\ 3DjweNXwh^7xTpF6}g0>Q}6~<`1SߔoͶZD?_ gᶿ\1!Ɩ c?R{ l~6_?2 zWt 26Cw8AYj qer1Xޔg-%eZpif)O]ۥr3aBT`k  6 ֶ ϖ@AW8B[s tt5Ǧuȷ^]wOKXSiˆřRc5{BwPWyGc}S7b80Nc -92Tdքjڐq \t%~uptRxj*^jZj c cA*3 8cB1bNp6*{$fMr!!G~rnʮ~5/4.Fx&v>[!nbUy}albM_\NB۳e|ίV׻ϩ"1üǚlv]e{BhJǧu'tWLt'|vك|L8:W8Q /"< ̠1RsIU֨uH uD)m@?HB3؝J6VS1U/՞/=AJIZ_2Ll\q[lY[?_^gYVyԞ~Ajkw:<׮!0DJSuWW>J8 T}V`kS-rӧ.=մtZuO΀Wp*462-6^^X3Y:@t;FF"X>nL?EfbFMt99zDؖ #"3xoוuC/PXw|Za%c4P+YDǧ¶R1t`H@܀H\OҺGNh%5Զ㉟ pq qcd3mji?rnOp@0wyMT!}|ۋ|㧳 IMuldncWٛQϧ\:Νo8?CxuF},4pN\u=#Jx2^į$7K*nVrh`jY`iSu$ .7"󮗦 >qWlcRn mYV3aXcueb+hPlXD֔n8|nh@dT7tf;@cj'UR=k$BSEk,nM \ /!7.H'^ߕJWiPQ6g^'ݏRW"T;l32Y.jgiM;;C hԫUNMj )6X-FQhWC9uJ5w=Օ^x(=YNbsw~ĿxN> zHB;<Blh>ю+֝&l4k4Se#*0oC(c6zͽ*HTzxb7>y@ɵC;Y$K*Ѯ:X].{kgqV*d^0u䳂Lx3E=հZ;G]IŬ6>ޔ{<x'V^]p^pIrX{&BJ92e y$gJ(czYT"ʙ38NѨ~o|E:p6YC;Jgի {A)YzFIFt񙖿kg vWsἳd-gÿz%mN5sCRW,}]d\51VԾ.IZTyxmxO.ȵ^tӧCު%>Sκh69Bp 'RKvЎ/77@ LQIgh+דC&YmFХB@uړS$/%R_dEb@[mW~'ȅD`9Oh֗ 70*}@%ǨarN=P}i.?[IuL^2rV~+nS7y3Fz\_YkgYv֡sj6z۸Wmp`ZpYǸoMC)"W@ti(zTySOrK䝷=KO°?2;q^RGsǾGmi%(oh^pF@1t 7|Xs$X]s>?jgA+Y5>+9vVivybt|]a5߇f aa9gwes ՚/;^|vk{.>_u$qǃ︹AkɩгOeo&DśMŚ=y]~md>w"55֟&^_&oyBtS)4k>䵶AfY2K;?wBIdr#I=$:Kl[10º1hAs0sg+h .|rQ47=ccBיJzD+/ ,"kaTؒTϠ kmĈL#T(nk"F+QKf5si:eR T]V LyH6ѐSE"6n69ObkHOÚ#myg`ʪdFMQp7at=ߴ siݶepz6qBlT9Kr|1u04kqYecm$aNm;O5ֵz߷sւС7Qj8>]lBll|,tͪg2ރjU'\6N9*֑Zr<+?"=wmtdVQ?=ukNM ?i~ rw/9$@}Uxu j*O5sEjjcTzm#tDb?qV1f`JQ}㼄u ROQs:#I>KS}+_Dgg[>h>Rb-J3fÇ3źb-1Z)tɦQ=tMőT&Y6z θgE5 <=E[v` {X&R 7i ^;4I$9["PI?Yj=b|& :G@>1 ryDmܭCQgd|AӑPjhr"5 srvcZV%v|e45$bc'n}wЉz^mG`{M*H^ }c ˝wm-7]Cd!sW߸u<7Gn}\ۧ4w+³XЭBM81ęMCdoUov^٦SˤM\p^7WHU$rZ?u5F`Iݎ|I\E\+E,)gYhvpCvXy5kY[@P-mJ=<6Ij.k{ޔ&)J\Siɨ(t6󑚉uFЗV 5 FmzyJfl jИ&LS> {yA^$5\GB =yR=1B+9N͊L  xbIn>g8 -)}Ԭζ$N8$)J%}*\PfK!4!)ߊR"&F&$"Pd3tcXL1%[I g"lN>&͜Nз< tJfCrNXrٖڼ C':G 9Hc$@Ͻrx:lWAG(gʺ^T)3F*h.U؊JY BtQ ] RR5eoFwSZMzM5Pu8sT|kK! #*u h2RBZ%58Jr 0P2ft5&,9>\SwP0=V3,U)b*-ٰL3P *BVK@5iq͆A&2K54\N'n ƗYb1&>y>R:8T% QEVBkMZEhMn`4g`ɬ+p@Qk2#%YeU~ >&\:\) nPpYWP7 ϑH &T6!K#h nOγѥdZ2ۃV@i kڂ!1y>8DO0DBK1-#+j-(ht[i3|Kash}>:TL6AIl#buՇjel,XstD.ڡѥU $جeV6D%~48Tz6~.NgͲ@>n.lstJyih'cIU18P2Of6]A|/:Et&6[w`RvMȚ@?{;vO&>]2+ڑJJs،.tVi1JJvEWLM@e]z@mjMJ ދ0ukvx=^W3sM[kV}{hgXYhmqAr\:]fӹk|)h&4 _ ,b ow@0D7~z2GtB7C+_Ob@obEb<gX$><ŌEP@tf|3ڄ7E׳ec!2\) C)9; TLF_:`UƁʬ7^N{YC0$'ۂOA]SnKcz֦!+B9ң>Z$2~(~oy :q=@>Wz#+1$..YVN5²AT '~#A" 06 4MMCŌzH.Ef ֠NIMTDof+`u/~ӶF 41ҘRCx]d/ 2:i~\&VCw`GfX^@r¢m#= Cc?Hi0r!D:vcH)$jLiLie)|)f9yy*NWsWK&ѱy򊆒tGEƿSL1)qI bDϤݔB,t@)$jᠭ̈/'ׂ@~_ŔRQ :![IkmYHm lz%w%6B8}բ~-c.{Km@b\/H|ՀiICݹtn7om33m-?Iµ*[)u)båRug)V5d7l86]n89]ڕ{(ʓ$L#Fܭ%W!L@?R%jmjAt\7CuIy>% ]vRcׄJL!PdSJAxTǵQx-摔{EޏP /;#߅v-R"_ Ec>bf,P (9ri bnUbڬz+#Nʭ5 QN(щP1׶d2[z~h|. b梒i)i-m85 b 肾|?mЕ PAej[Tۼ׶iIj-(BLQf2>+(# -Ij9H7V*~PUK;[Mbhp9ʕNjuNZ6^PۇA Ds I0JuI'5靖>@Ѥv@b⣖_ޗPh%lgnɶ1/"P@x}RP7V&_CV?'D:P 1 9?[d@qas[$i7[h"9`Ƒ!6ǒ"$vJU@I"6 ?D \1ȿVk,(@4LD} :9"5@ʎH3< f3A(xCQ]ԇC-'Zvɪ $J1 1I% 2˜'ZOiMmF:k]ɒZ1ZB|7!u{J&ujcC aBL&/f_(~Bh)dF$xe-*RL6!$Z!iJaz dz#Œ:] UԸdU5: 6v]"ޗWuۗ A~`v 624HAMtY2GJ"R̰"怜\` IFZzH$'Hʳ&fZ"T D=1h@jm#d.䖠eA $T4ZNBs=ٽ$S3eJa.GG]xK_S"($UC~M kJl@==5.NkR+.\e˯CivPvyA~LaU~_AQ 9܈%Uv#sAD;I1>>* YcJX$)4Η@-ȑQwX= 9UP4_G G{f$5. 7j_[P(7(^M&H+A&PP]@z(TE$ZnwЖ<kڜ uٍDa4_rۿXJU2z&%?TT(lt r}Tqhrl5b-m>P$B*l"5|,mP ⸰|Ǔe ub~[oqͅҎr}wyn I\)nR{@D\Ii#͏CTiùk}Yk?^'v9E8HO3"Р$gtn:*[- cy/Fgx9j:М@Қhn-'_F5aa uzÍ 9e]MDA_&ȎCukZ(XۥyCGzII^߲Qk.|#=߻ y `Jm1eAFkEOM#>֐Lד᠁b]ek}iXK!A ڸ.T9[HJ$@;<J;)zbtNLFiDcAj9b1^"KƺX_3X{ *mm| v7݊- ұ`*@.1Q{x{;n`-6sS]85Q8BCesVNp %.9 A}p/l7溉K<3: |$M=,fp `-! <&"e\\EZt z{jѕЙQ>(Mf1=?尙VxiNCg #bE|j 䄤7Ab[Ƹ-bݎ\]am b#u]OAFhBۿ sє) f] ~Kz- :(?ܔC{tTr]8!V1g׽bK0{ucq"n/yǛm u8+k-D2p;vִd(cK"'̑ˣbaJ(C?\A> bӘ{;f,g($ (_+φiB9yP)Id~qL;J՜دсwSCr.P9P|On_#:*qSVcފl;*Jb*N\lEDPtxJХT}ocN3ߣn7W u/v3=_!i>lM1_E<\-m)Ǘᵋ6EVr ĩA/Wk E:S[~'q ?Cc#>+OJu=0؛p:`*HЩ OAt*c<1}8^91Db; T蛭\uSr\ RC7=58FΣAB2|ʑq A`LٞGsjQ5t>e2@? &/a;ljV`pJ# &`׆ Q5&5tJT8ݯ9c.uC^P&grg)FQzy"M6\wre:n%:X=pJ\ϸz#x6Tۭtr4+.kޯ1F%Ɂ}8&*Ҙrc1`S@ j% N>ss1:'0NAS԰c֣@xRxσj#ēfp72p"1GBW_@\Sё{ĞsQQ%fU"l i/4]bmͩA1WTP+鉹 qI\lr69^ 0^1,1{lo ]@Y1˕uI'/:U_1ah=@K\ |#N۟`N wϯzM%!ZPR'VkS Ae5qHkA @bjCjqjx,0:b5>|~$_p[ܑSu[wg'T"KPn&)`2&@ C 2 ˣ85t'UyTgCzas ?|ys"EPҤЁ]h^-k q3a u}& AS߀X rP53 *p=[ԄjؠӆNP E}SС.F@uk.>yho.avzxVk#9;agopv0':A+f:Ls }yӨ,.yT3c`&&^:݆KʼnZ$G%whQ;85O;ˉ\N (ɩA` @|F@!P"k"PY  $q/5K#)cw!'<Q +` <;qNt|&[LPM\*C8 ]/A}8.ˀ!~\/wh@ rY *ئ#NEɰ>cr<59CwRqi:C K@йyj`RG/d0KK5m۸ػt.б5Q$ksj~YkaTS h)qo=tӗ\1bkH=XSj>W P\6>!E%Wfp]%y)*XΧa9;Ee0)X|#}'Ji҅U)F*KLh:N/C쒫N_j4QOܥ#NkDJ s=m)xȣG-FǝAE5`^vJ >Kq2ݵ[%p8\m0WTtQL_FŗB 1([[ND&7׳S>/r6*P=n/LasCԥLx&R8e:xp<5! }CWEJVJ6(k(hTDA7Mu 8`DxC, E<q*J,)P 25_rv0gT5A)F=j0.19/^O\&5C&n>{8SM\ ^tx,nL88VUCo+xZꖴ b$ 1Pod|2VѷkEɞF]M=\-[Bir(Z7ZvptŇ.\>0FlV3!/JcnqNI(. I A%vS(ԕ8G- \ ġO@떏\7a>W$Ԩs$Ir&vdh7vPcsu1 P>tE(W'4jR9rBTK9^ iӪCU oz"V/xOl}|N%zxP,c8P=qiwy/X8 #-EE#{!yr l K$'qcė0T| ?Nem@- W,K3]҃CD]"I >WL9LX{sK5=)9lW`I?lBJ_G7q| b;h5(Rm꠰I5(3AyJ⛙ĮiK85][*@>2+rY'%AQ<<8y}^#h@ Sw\%l/5Pwzlk2~W9N랺.VTjc+PITRh2vuhu^\gx۫8'ԇ8$;KFk ~^d7ftr&y{rg1(E!IkZ*d3a_8JԨ+XbO%3P5x&scÚ4TA:.Nlj>f<хZFu6A(< 71 U8)ru98P s84Q\ k`C3e9T V+u /{iPo k5JO\iPc5*\1bL"d;u22dD )9jt6(@|#sfzԙ!)Lz>^ JR;>E;7/#REWp5F26 )l0x/n ȣmW `]P yOG8sL:m9Z>EP=I>T'(qci4EoV"yo>4KOs88 \'x^™rV޸Yk8Gj~*:x}H|7=Brj1! B8sb..9Sl{\>İ8z} gGA rͰFl y.%2k[7'F.x:ؤ ꍢ>S31 "isc`nk ĶIlOJepcx)`<^_[ @Jn9@A,vIY8')Bz貝"74P|ù^wWBSflm8"oݸB~Ӏz*$+ޭ-)=4;JBLrװE7_˩X͂X˸4: g rjJr_PFd$Ӝ>fwVq&@)*vh 멋w!~(m9)/"j(ŀJ"cp )1廱R; yT̏B[zk)9Gk'((Ln0$wQ7Ss*:~z@%s?ЭBljX[!iY%TB~qr>(sF8r!^y@XÝPCƩ:$ 8Fs8Qd.Vj10E9f,$K?kQw  ~oR u.:H^4;'yx~!5.q v?3P{tF&QMv4M/L,'LJBeNtm4W1{ >#}2=qN홴ztz.اNǵ퇼 \Tٹfe @\\}'ڮmVGPȁu&qrW2**XHLlO6iӛx=Px$=B5Ю#ԭ^3T Ab>aT."X\SqJ]if1I(~ЄVBh>g2e/ ysTyP|1e$O2OZ$N>Ę ϿPL#‚D$L~liifլOhý1[p~#2T-e5l .2abO]ꓐ 5+*?+~m,I魤V=%Jlu0:dz5f| .RDs~ܿh8]GQJVUF'}OlX&珫ZoQMə(,=(xp@פ,h[N?G AQnqIQMyqfT47OIUˈ˜]۝YvQ^/AFTČEi-R+߭ߤX ԃ$DS{\pJz/v_ʨ}Uv|*?Bt{#?Ln,|UD6˺JJoJۋŕeWM!>Mĵ%?@?ꠘ'Iv)󨁡[u5QZ+Ooً-4uˈݡ/zFI7^w{m. vdNv})@w+>^+;Γ$tv |+&}OG"*]rIf_(l6g592]aE=Lf|֦ r-Ǹī&J ʯ?˹"ՠ}I+|&P_UJqڲaqYAqigk뚓MbqzURz5Q1I*IFlfbH}7zK>{KKE?z0t>7x0n}.RgCʽ#ˮJ k=qIwq[Iv\,nМy3xXc?8"ikwͧEҒ*[K7gkJr3o:̩w%9x=V敜W6(k2~Df_Fdaq[OW'j)k)nZ?Z Jf*(Ä(\X5`syדp&yM#on Cj۟t5s7e9egeY ƲUfƙU6 x0oZy g$9u%[?'|_M⚸ATyӴ0̘Scv><&\8Fq-^Z/|Vyw<<ʸ*P$鬿yCʋ.8%l,xe7Ih=IW^|?Jw9|*.f&/zaNG? rY]~OF$;GW8Gz܌vc8\"@GKP Q[3BM 5|}+OՊƯp `W:[Z;n!SҸ6&bAISqENT~aP·`?xb֖:2H׫(YGc`iƘZXZ*ZiK i`7RklV(*z><`ϓ Dž*?C0l8)}pVZP}Eb4?5zYK[SQS6ߊ=|/NQA}9/TXQ~KfiC=EsB閞ktQi$MtG=CywDjrq̥$jz Jr6AW| +Jj,*-J啖%%KmRKC|J#6$xu=&8'x 'bӎ[ o[;%*,mu1]@K$2nM@HjNʚoHL;yL؜rUp]GYU> . oh_YZ]BchRch\S[Ws/bUEĒG]f&%E& ަEAjDr$I(yvXZxY}fҚZW `Qn o:[_5bQSӥƸbMz - 3+ |;ԝj]^s%-N_t9Ȍrnvu[eWfM3GE%ԅ_q387r`^={ }A_wwO{YaLCs*\o*V%;=T``v;3Ѱ=fSg{羶UA^7ãJCR ]BU 9_v9=XSk2X'-mn=yﺢW6k* 9ߔp9=C }jIjIv;Wj4ب=aV7OʕQJ|jJBV>f5?IL܏<)Za 7:0uJGioXʈΡOYGb*B_h֝fޕ~96#&0d[Z8[uV݈mgyywvNnuѢdחkdwk~-kfP>ܩ1K7cvqZKύ "`:zjJ2D0=Vχ?4vkόryX&(1{G:xo%p'}_=*<#^| + H6o'|b[R.v 7^:y/4.&82oJ/_/bU l|'i=+^دڇvGTƭ{:%Q aמOQO*:VnoWFw.!.y^a1v8Ә!q7<ږqQ{t{b ]# ~Sn@RCl^6ZX}Vk8Z9{%^Sýw%ǍfN7MxШaS׉hD 4kZ~!tj&AۨÒf-#s]BS;p 6&06'´繿ko$ͧvA#\CN~ib~kkx7>{@y[!3Fs?!|?i:^qSw21ݿ{ ;T' yme~35Zc? {M ۫{WL.Gȣ#?:F;D8FE|ȷ((px!$7Bb`6VuX`#r}ԟߏȘ?Qab4i[Ѽ[yehC4{^j檣Ѽ4Zv۶LPyn||m'-7C;cl^qfpy`,V\lR[`RPh=ܒ-oo h2 C{p,F&r+)YвuRbix9NhbZNY yh:3G{o? 4c5>~<;gH+wEyNG+ Xb>Pa_c1PkSSb T'+]5gp86O8` 7;9 )L]m*4s:vS i{i[i-NU:h롇;}d+J#Gn}Q^空&. \{mb[]|[vKղ{_͡"nMc4/į-DN״QKЌk6Tkl+EۢehVh =~P.e7QEWzxcU.qEm]m6a߳gOo_ =x Ha"<9x=NǯO{{r˜ͭh}hb-r-gj8ejh%n[ 8Yn{$*^?{TU`w(97(哛1U\޹&:g-syB}5.)vm_>Z}8El ዱ_W nmNßf-B-A-¶_Wӱ_י"hѦh^{ -Bmw>XU8D_!_Wu c+ɵ|QbsuTu\YMLls9_d 5xgstHan5u`-Hcb.A sT" -܅7|"S~_Ä]f v?N7޺^w=K5\eYc[gZlw}|G-Ē[Xּ}X0 ).PE8TjV(L^7{;g-+(x1Rƣ G6;幮Ia9 Jbuc~ d(a(0a8!6C`%r( <4Ik\쏗m2G5,вfh*>Zp/ܩ>o 7gƔ:՘FD컛nE8%YUXGh&sFrH8? 1:.E4}:4{.Z{]] xRbOf ïkN;>ף|j6@-j=o=#Zf|cT095ww}6Z`SM@}lM]$F;x/ Xa\4ozx.Zy$- &{:#7{~χoͭl6o0Z"w. ײ{KT|ge+4kYMzVƊX`z:<0 +cgʀpe#!$" y.( [b.9>CY*hBu'Z: G[NܖS3q-vrJvJv'ؿ^uSo{a;Oסt>ixnDKքs2Yiy iZiW|? ISe2a$~ÔyT+=>&k9NK~ ;mEoF hdI\㻢ZК$ZV\*5lgvZݟUNk˚_ؙ p]OkJuN\Z;VR֐WR T0pia>V;}f&T݋RaLgs_LEKOZŊJƺֆgx-Es*e[fSh;v6hɘ;?LQ.`WkdįFvA{%& ?X}cџ\X-yhgue!?vߞ%xQ'Dxq11DC}D:1xIQO/z%i\tnw-P؎`{m?vYvh}aI_Cz?:?X&vjEU=jYQfQP$|14ް5[X ^kb &^|xC b`|BNu%1*TMbySDKuEdM"u KK,?֦~Ͻ/Pdwzr}u*acTn}(`/=,2gy<ޙy.ٻN ^Wꈺ] bdpb#n(gT3n0UۜI*YO {^5Zb.}Ym[Svh"ً#j?؟kIKh;ԛJ~pU"Ouʪ޲  _,7|S|N~&O5"BvRSQ dkw gd9U|=Ѣ[9-Ӈ.GدڂVl2a*sT_k}:2Ϗ?ڜ=Lr P}WoЅ%f{&{@BXUEڻv"Z@#Gn5|MèW=&9haxf"_z'WoEb\H<3{(YAIiuiQVD':%w^ 'p9yZZ =7#vvKa~ybt:,Ŋ kXۥwv R # b'Ǜ9/e3]K=p4oGDDVۺԋF9?jO؎,FB/YQ\(KL%tI61tU%qay"DutT}e^'{HUUY2u$GӇ9>BD(O&;>m588DkYe𮍆I_wF~39eAqEޞOYߙGC|H"5L(ȹM͂輭d-wۈ^5qCjqu7юq[ǍK+yKs/ nù Zttfh̙M~n͘|ЕyH>z{@ɯ_Xjh9(~]'^&]c;0::z ꫨ#mۑ:'B\8%&D<aA9G#"J>+)+I:fiUR&tsw ;SQ5{H녩Ԥ픤KTitB-ċAڸd}0C^vKhg;fLVDҍV+bؔȐo3/WWhDz5h-`*2l7ݴ'ˬ}fWބn_4]K{<o"w=~X&*V8}m e4Cа3Gd hc+ޭ%v+f?wmvFK5h:V kT2VWfgD;W/DoG:<D2>o?zh^IKgK'/'.z*ga3񵻉["e9r-ĶPS~`k7uDxbTRCr'}`M=wRnwS ^4gW[E/=6ϧMy%kڏmOUWfi3t>:R۩܍gз D dRXC t>)B36,Rm|HuNP&eseś KߩOj8"Mh0LF#O8Q*ͪ;ȤPQm{{Auȿ?m|0Aw=?Jك)7}}zfN#yHP WC&jHWK4UڭHs!-9 Mg?V7@23yMZgN#sM4G5mL%e0铊0hq%lxa*tf/ŝuV; 9c<{?ՌnS6,KP2mAƲ,ϨoR¿zsMͳf}'OEn1>Mh=Cx0hhF%yn4e^z+Ӡ;zѥ-~T#=1͔^J&23Ե(lof7>W#:L5g'_iPLa9QnI=&R\'1۩=2iscO:GgU7[oҡ\lj _,7Y\nHSݥyc0Sc< zf@b^%>/+ȄRab>:E z@WIjtE.l풴zxfcVtísrĩ}׫׫UYHкGx&٭Ozg m63 ,|XH-*A+瑗  ^ʿ?QU-5 iHmkq}yBa^3I%ω \H]txR$_瑑f ~BMN*v)DZ'MbWW}VӅ!ϧbwW>7$vɨW]RM ~ _w~OrϘ349'/uǍ;ʊKm^a?4OY#hRfBuУHqFf. WٯaOiF\H75_!:sef11ٙSW@ѭCɃ iDE#- vQ)]א5Ed@%թI07+|xQҋTJ&Ac/kSޏWɀ7)0NXſUZ:d*1uWň)j/lݍ(!]ZHhtu:dtt4v<Wq~7<'똈ʽnD&g"DN zEbLȓ-T}KDVnd~f@ dDtn"n7ܫu+ɋmV#QzaJ&uQ'" N:'45(C<z L,G@=HuMͥݳVaJ/ ^sScFCHatN u¸p?civ!QVTSqF(c$ /Bc/} &5]?㛪6VAp1cM۪a'f31ʙvBX,(Ȓ$  !”@f ZJ#mK/-ehK)>Z댒ȉmIH뜥kװkݲ%VϑסcX`-] OqZv}7\>xwXwܪoPfL es|i۲3|=+NkJ9()K w >v~姆[O]p+?06om~J٭x;s n}y26ghWHEWo ¿y̖_|k0;)S=XC#<;xy7xVwZ#߽Ρhy76iyCλy3|yg>U8̵ؑ'xy<\h9|vC`ͪj f|Е2xˁ휈4$=wbs{{ic.xz1(Y9ō0| ŷ>_?6|B|b?|h?[?Q;#z6Y\߫\Ϫ#ΜY3R89_̻E-߾eS.:sUa|k(cޓCwo=r=S*a>wKܾ5[ojy쪆;BU%B?{8@1'lt.0{nj^ >hq)˜hD%/\ET#ׄ6+ezK9EّW>0}뗱ϡxow=v<+J"s[iyԲ6ߵڦWM1Hn޽Gܚߴ0svU0TǮ޶3(vOXQcOݵdۭwK1o{izOox*a :cgKwG$6uWB9vgaD}cz>ۓ\ k̫jX6s]ok]tpo7DaLn?K1=3[;!DC]<6Pl}P_C۾u.\得ynݯ|w?||{ObE MUwc'͞\|Xd07/8bRJ_/\|b( cliKw_0vQ7k(.3Ё=(^oOl{raΪ@QNW< #0ߖ;r{idk)sgE^)Yǿ ?3GyJ`?֢=>|/n ޻D)Ӳ1'<{T}"msf̂/pNvM -NJFRksḦh|jm`xJyn>7rvw3o¸O SEy[(zL)}-{5Q>isЙ07k*Coc7ٳ[n ]Q,}x"杢a.v_ֺfEosօ|TcS& tY7%791}{Su[:)ƃoIn7/|9rgmY%3u==wV fЃh;Z]m OwOXˁ06\ʏrD2:3aLo{Dōy@N㿶Ԧ1_]E<29Jcqo9r-濊i#ȸ_ :Sr顸xřŜ5(o=j)ǂkQӵY]xsq}0sg-jj|&KO<]PEκsgQR#wVl) wYx^&uik(-1e/X+BN{$,,Z.Ř8)6k~t{Ot3ψ;/x}/ L{$-]N599(1o|q;ǵ`_*W 깡/B|99\Dڻ,žt95O_ںt o]? K{0 9oR06ʝ FƵYW l9Skl)]+Nm\~ hMW(KROEyO.~,cO߷\[#.{rkvl۰rB\+X%:1Yp'`8Hb7B[(ꃗ<۞:r9$iI]৷Z; mpz! PvڗL|Go^Ȇ}Wbmه?Q[mׅWn;>cg۬k8;sQy}!hy'bD?끸5j 󄫚vHl)d(hռU*ZWcc+lZ*?΂1rǵ?y%nK~gj9)БN]C]91c^g>9̇7a~/Xs9*B5|@ csS~\q-ʑ ^\8b^}룺uyaJz- ^,~M>72΢/_;+QU0w sq.FRkcuSn_Y?sgrgsgYKNOގ:/_oNZnb̭@uA;:yPg?X~?̩z#@p)?Yd^'/s[}1z:}m5NEOhjMd|f~ʳ`=&:"߰^\}o]*07Y-UsT/~Ȗ'c._+An<<lPvVPzʝ1zYY}.Yۿ'D2Da16x/ж~muߚO\@w|+9yi(=+;/(^/]r07}<ӿ7v/axZ Cec0<=F6Kyǿ/-p^=q₻V%?u زZ~I-wrg}vs>背Y{s]&xN1rgJZMG/&~nWW?u7`pϪ3W 8 <@yc6>gQv1׶߸.  w}OV\_=w%v;/dOΉ(B\~]^ixXTO 6c;Fֺto;&%sQ=tG-0U׍1 kFyNWֺj% mКb΍І3&δU~ZuZ5gF9``,~=gP}Ձe流~1 D^9Q~an!+ڣ=u 欦s.;>)_oPfCC;@n>Ah~WhO]x8sWy\ӈH/M]-ybKs (G͚._ Ϡ[dݷwX[|ҹv›\`<%'>k4=CoY>D]yu.x;~]P-yQd՞K/'[k)፯]C6O /|ςAraޭ-6 =UU'~x_"K=y:ry» [$/E99زJL\[Hߝq-et To'beCo|ZʏB{1mY gE9q Ŧwyn]υ74_зjpS_zZ2\v`zmu(@+tكkwm&7͑^rc[Hg/D^Ř+xLgO'Ы!G]*ܿS}hOo׏ENE`u=񘿂r\!q\KyoIFl{urs[x'@w<ۍg sp_x}ʯCW}=#xGG%} H{~^<Ec 蓁kᘧ(pgOsqœ`N w89ӆB0.py#i̿?\C=HugRN,Ѩ#aZ76τ7E65%7'"нp?汄E('Ø>u~z"f?`ܫ\_K6K'ֈ>@_z-7{Ow'7{,فc昖xŴ; 5yeG3ҝB_m{ u X(șǿ3V_iﰁ|}kwȲϹ&c_s/5{.9p{޽}E $V1xnC^2tN?MU\ڠ>-g+U軁 ہeO3o#)ozߖss/{d"ȱ$1+cNg'vS>h|j"] 8ǃ#} <\aX\g9^ [ ԋp w~DпRa|b?:/t핂Nv/}}i7|EGpnŖh'a:D|zy1! "hu͞H.m}r]It Wyzw휈~/r'ʱ37[#<ǹ_ߵ$<$r` G=t&wOST>OST>OST>OST>OST>OST>OST>OST>OST>OST>&MDUO#}s7KWϯ66oSh5 Ay 93\\t&MqMwM2mP~eܜ&\J]1'i&bڣ_uE%Duu#Xݼ̉6p" tU `EdժkW_]=G]wQS4ODjJ6LxrLXd//KǜRA+cVNIzYLy2Lr«n'b9dVQǐsZ=]Rf"2݌جt>AN)'hDأ 20 XCvb5(1ʽO:TO,ͤҎ*PԬ0b >;xR}tgQ9Z`5>ݓg ;#Ғ6aXkz,>*&XVL 8 (|Mt4TbV16O3/4Ul X+6`DgY~1-˙zR0 R2)[Z,6kBec\*Ҕ'3 ,0Zẹ]^vTe km7U R1R,~e3 ;tRSTw5FBDXGps]z!xyK&=U*6:^׮_M&c O,k//8Cp/8jғps9sMe$LޞD=KfE{JO&uG*%a+9^ )̸\yl֥Sʹa<^ Y_aKiyG1RaՖ`fOo@̾΂<:D!k1VǽI9ןRF:өnB"s(u a^dKCpYR,M$ƴG&9Ʃy`)18=Eh&祜{ I⸗b>KӣұRdS2Ri:F gK ,0FI4.^~n3JmL!烬TD*n32c^q)9Jof+/G/vTq)"Tq)Ǚs)Sc4gHUgM&L{;ΰ_ǙFL1L)90{L)lO]Y,`8U:t`N_\;ƶ e2vN VتD9o$k%[_':rE\Xs?Ӱh^9W+{Ε"=ʞs~=ʞSs9WEi9w;;4)PFhYeFhtҝj[vQG?H:0Zk1`csgg}* `+WܜȥV<pwct`ĻRӗSXfz:1=&ڕUY Q[YrgU* ta1w_ty/3DK31'9)l%lgF,c;3C3Stf]z'c  ejFR/ٔzƌ,x*`1O93M< ,Ŝ\їn}et*9ŜbNe1SY̩,Ts*9Ž3,heʣI4U9x,Jj%Zrݪh%UB-GStg%U%U GYH-z*yUD)X۞u?2'<Bn5"ӣ8=F\F>(NP,S#8V`&9K8O%EB|G]ÊϹ5V!8ׁB?aqC,.VY(ysD**e+e/"_he8v יgZa}lijwlLy2Lvn' Uf*;!&a.)5{t;0K;7 rJt9e`%q;NȒI9nf&T󞡇@UCHćr*@DSJ4]M*8҈$%`RcyƁ)`)%+)UKb_.?K3ˑp~͖ W(0=ZUfQsۗ -Ш#tQ]86s'g}*ٛn斫"8. ;f>"G{%ab )hmVb"67ьFD՚'PȦE ]7$ɥURWIp3`/(˙zR0 V4rSEy1ɣO՞sàu²t Fyh:Y+ |)D<(t虃clsL7"Cx!b:٤]<ēvD1 4EOEVDh.F-)r1G\оt+I%X3*שV$eXk$XJOY.8HonIiR)>~Uzu1]hӕsh<^T]`#(3_)e)K;Hcٟ)"5b*R΋r<]qG[K>.ʂ Ra%`Kdbg/E|8|TTik3XzV<]vGŋ3хP k+l/K7S)]1fW7Q!<ݒ&{;g(_+'ڲ}9j<1Q&1&J+ƫM$F|94>stream NmeW*N44 1nUS$ǹ9U0 U,+r 5,Jth=PyP^Ĩ o/۫NO,MAR9; WHl>HlC9& *("GxQ"#( 8DzTFwyF Txt2kìFgYuR TT D26zpq"4C! :Ů%\šv>$ov h:xQeQUr@nf#YǓeG`,/[]"@ #OŇ =A g ɜ68m0Z_&8JCF!|AATx wX{2;,Gǭf34od Q64tENG}`pr`*g Uxbm6WeVw|q Ł1 Q낃 . %8zdugx!kf㊏:&8XMpv :H\uq S}h !8xMp`]:SпDpdo !8zmQ5Bp F&8MZ0WQ )^y2?S*FPf OGr*3 $-q7C-pTfXoNpP IT\0$-8 Kv -)qlS3K8b(܆V! Mr*)Ȓ "0k%EԺ-5`0:;i!TIBߢBVO !'܁`!p ͪ]V E"' Cʉ+R5iF@N ېCaNs`*DZ06E9 /itgEмY:&!v%a\=ecу58cuPck73vkk߮7qβ]X˪mp9s=퐳~ OATV)pնߥ98Pwq- Ʊ^wxcexXTOGjQ-,gLN׬tMk"t;`B ta)tF 7KQD^\Wk| w]U" ӄZhmA$.f5h *u&@%Y17j%i 8'%\g TB0"xӞ` [/Z0,H* 3.SLKVa9N2AjNJT_ 3b aTj`)T7qʥ3 GRxuK(KCoP@,f ȇU} 0Nq#AMS與VfB$#(IұUx(*2Ϫ2y0J<zNt1($'I)SDэ 9^"e8y05<ϱ0P\n^TQS14JcDt ,JT"Ȫs$4*}b݈<쏑dĄ얡xJb6TeYeqٻD  dY[9nTTD`GYcUz$dHYQHϋ.M$`Mj" &klqCӸ9KqڠZIŖ)*d+0as V:<) ;`[3M%R]/w!_)8ڐjN[zMJgr$ҙXGO.6xOg9^:,hC<:_ƛHD >£@ Ay:FSӲl@Reh/IUZ6[ij- fi\@B պ@k- Լ0.(^kHK'פY4MhQ0ӾhNqeR.Wŗ,2o\K/uQצ=kcV.5;WfcjAQ+$NuE78Lq!yT[{yK@ ƽ c7cp·h B #jzρ΂LeEr V,-gbc,|:򨄤J٤=jHf0p[TlȤHf"$ˣLY""$;d7˼b2]JnMѪ)fLAYj`f,8 KZN<ڐeЫ6AZ[,Jx`qY7v&Mpf6N1 dRBz wۦBd&f1O^۽N&fhI6YP̊ d&Ȣ fTlbo>/&'51 ͉ỉCvfь3)afh3cT,2+Z`/bsZ2p0 E>v+.vu̢12kCӱheC ɀQqoɄdAN_Y42n@Ll:m:ra׿,drcHW,*1T(J" 1Id:FK- (X &z{B԰+\ 3Ne,  E"C'2:Y~oȄ$AemN_㭶̢vQu(G80}`-+Z1$ l> 9}`cַǺ?7* 0~m\]1y @>O)ku!lhalhsErl`ɼ l D;-R1St;޸m8Ɂ::lt'T2o%| D!pQDh{%Z `dr,@-TV A2B6arl`*k+Jm@oby2Ζ@ldo`¦J4 ,euim7n`C'xK6u$0uXN6b·w`'x=}r~(_?裔 tm!Mhu"s(x{o-"*I tMDnୢu4ddӉ J\FM7,eZlJP(h jYvM"p,}7vZt,gCʦfQM)K"06l nY95(ٌ`V66ٺYMf^CkveddM_uX\ kj]tZ}S-0Kum0KC0KgS| )YF Yf YXY>uVPt,|;RN,|KA,|[J,|k->Sr%#goKu@sH[FR<X2ϣ*/e)X~p8g$GgUY֎% S]hMKQ)Xw qmFm$$V hZ'D+]zc~.K1U6]TIvSF6!ԍZ V۽;u6佅&>c&ިŐҜ[{u@fh!P27*(M?ʢ`|et91hUd SXHE/2`pn-U\ʒ115$3X=H;B7h@$hVmG ݬ]6j޵#: Џ8{Xx9. d(u]xGk6rZn]hg@- zvRoGcxzHhC.4T< uv-Fۂ̮r0X'Y^C"m%7VQ *<?Vw$pS09&bMi(i4,hE>dGn͛2D΅Z 'kn,3W^TJ jZCB+ ӂ+T%+bJ @F5΀4 #! B4UĭYxՀ' kE1!sRUJ 8ペ1 @I6A"ĨKj i2GnhUWk}vl$^ "nƹ` *^P7' E{~Ok22&1`Q;C f z1ZWa42 \²hC r~q^h XQH ,eZ*u,(C5CaF9l<0rвwwuϚ7 gb$5kg5{.h4z(DF\/t|V@d֧9Os+([+z/ m,*5^~?Dn$l~iWJ?4T^R%83 qO%A@ʰV)]4F͟of{|US{TwNx՘J``7.av1dĖejӱh[gw@{PW$0r" 2a< "XndYLsPPf]ځ97QUm~*IhИj+z=Ҕ胿 gt_"׼h2K7c}4YoןlLu񮞌՜KvLvzrv9jEWUf%y I ꢬEhUj% qC{4@4sm/\'bƳzWVOU@> 4*hm֫7^f+{'`pӋTKg 4p-v sҞkBΓr -I%(qj@Gz2XoԹ$݀\퍶LqM!4$뒘Tw΂YuFO4fLT㩾xo V_'m_\@ uxx:8EuMvMm3`VJv3N:J-l=-9oolXb=-5#iJxf\a' AU(S1,zo!Y6[c@ Č"bf(2(`fDY BQX?z(鑚ɵPiN(hC`juX8Qv*UV,Hԙb}1$N3<6&'uvL^`N"GFM*>U`hH'J] @JNptK<3mAq9^0 ?ؗa4̈$ڭCfTjsx >xǂz **4e3'bťrRETDKETDˠC8U$STdGR2 (hUJ'c%r*5%Qu諧4ye}׷KJʀE)n2{6N2#(*'KDF1DĢ4aUSQrTMsM s-B=@4M~ؗZa_URDpO/V8I}1X+1+AgH 'r"7+sʳd`x8Bʖ$ I`5R"cgXcA?d0!EdNXLƃ%?nEa/K( 9b脲FCTMi}AO)I@ƚG1[ %/wyNV!c0 0SHGntJ@-Oz3Ͼ) V3(*2+İQ00yeEa)fp߈lM_xL8>:c2yIF`%&8K"q,'URZsuyҚt+Y֥~1/C[<[}/1qsuBp8vDOnEp\o6qƌ9ʅE[xKο$AQ~kWKc(2d bB)orj49fQf°نGmgI`U"SI4[r)iNY.nAd?w/Nmzl?0ݼo&-N& ce@c(&?W]h^nOmF99au1\ovl1%Gz7_7u_c- "1`&1Z]u2uvtO4TϥAѠ;_Md>$BK]GR!a4Q8EYSt IxYO}V3=.IJ:'j3ͶژWj;$? eu52@o%cÇ=fQ'ϋugvGX_](Ih'!eLW&G=&%ldt0D;r{/S|q8Cmf/Tq!wx!BB! 0xmGR0RJXɆO_i9n ̇Hd@d._GIv8+MM;8~x.],YKs$bZޥ p&<(TD0{fjDRc%Ce. $L@E4V˅I =ހ$ =!t&c*\V"pnpb%E `E{NU)¼Е~4K;W$Eo(GQF `d-| LL C6l LH(w)chA_@8A4tOlpI'A.!"(r􂬒b%-E$!Ki!$*`DFP* aB|@cFEAI9/4<_@b~ǧJjJ 3#H03`ݾ@ ɠ|PXn5X#>I ! -X  -!aDwx@&N@ѝRD"A)G"y1H#*Q4?!j6 # Jd62Pzz0ad)EP,W0\D u4v X<"_J6J DEɁ=`Dt VR "f]-[Y9M{AȢt0&ޣdtlCbIp>8. e"#N4nP8h%_z4ˠ2 e1PT=dR7;:؂AB;AμAܱo @B0 v` u( qg-Sǎ&`, wPe+AlceG N+Vsڡ=~AQ`ipx@w'y1o<<`;M D^HE!x00z%AD эA%Q#g@Q*j_NQ&~~%t8 mx>DX3t/1i]+"h^*ES[?^66CAċLy1 ˘¬DCfC@ErHM3p*\T^^#uǐ)A;1 p\-\ #t7t>k9H Y!uX'yWc@gڀCH!GjxE5(Q b(Z*#ϜA[!1fl}j])F+%ށBBHWKSVh xQIBpHYQ t/96"A\"lH]B:+S [ 6gPO/($& , 'in̅'UyP5042R+5 4#v^k 0wȃ$MRhRH`Ev|=0*Zfx3*rD0XҨv4{@bM"Gzع{4&!P1;0e,3^?ϐ8K>/*_=$c}| q0S"/7K A9R#;$Н⹈>vf4n Jd*(kL/f!tD4ṟ1 Q1Jy $1NhDښߣTK>$a͒7>јyUFuO'_ b W.Lr6..x~mQgK+w%)U:r,@2w!‚$Inҏ0 1Bv2́3C:pPP4yHB&/~ Ĭ}XrB l o@LE//HtAg u1#xea ^ R~<ూ[e'%8 O! "7D$CҲ~QNC$no]&fq8x 뉽bv7:=Aޑ/gIcl}Ȓo L*Ug({' [e[`90`g&xUA  P 8 ĝx̃ r_Bwԣl틬Hw wiq‰&/ų@b߇DןrhR?,XC@EzpO%r'௘D4HC_ '6'A SWFt1^ p%מ8Xb_QŶr\_jl U`tF:A8eng%vgn}0 vUDA9"Ѕ<1J; (!#vȹ}M!(;%JX12PР(-] `HR^|@ B4 02 賈*qG(n=>OB6 NTHAlE(c<9Z2 $ C*SA*Ps גQq KʓVx,H{oGzڟʩs1\HҋQ7`n|kKvmEq[\ݯoQÈkpI͖!߰*np6f'3tbvQEYeFn6 wD< N;vXթ(6L[aM~R]#g xnI8@ןX/a6X|qo-v&"D.ZUۢ7P Ld#Jr14@ٲ?PFl{՛,(i`liH R?Aa$? :j7s4ػrj9> ohy bF\.T7Hfck7f6b7Z,E+?]ZcdZ99BÿW3 с$ACስ[d%P ߟF=`ЇO yjT4yQ[B;/GzQo`XaRm|^F(&\ϻZT:d6m,|(RlfՅ4, ln8odS7LƲ0Z,+g͗& YolΤ i/ FfN擿pؿ NQ(̇ #2'i,H4$g9CϢZ/Wht?v*C{'Q%9 Rpf&>h"cJew=$evݏemڭF tOߥ >3m&/B Wc3f]jP'0vxCU©M늜?X\:撂DQ # ª;(ݬdr"1Ojo ԝM D2.948\jb͖-d`-,f' DFFb8I"ˡ£ M)zMY۽1ž#$1/ Xl \IGΑƦ3f=Mj5M.Mic'.G{#mYEF&@#r \c:YuݾJ_&+}ZOP(a7{"1tNƢxG% H>I »_"͖@pmkOYt8D$#|oLTi*6"w%Fne8dᠶ^TҞ(?V[Xd@jR!4Pp``%5AU? DC굨7? 4,[0Zz|N)d!خd\^J8 a83s cs..۞ԁ~w6$tLu,H#=P,E2: s 9x$mP`'`JaODf<"/Yqn̳IMB\Z 5D1Olu~,kyF1[Px%0 60Km iiK&ڀ"^7ʰ[,R'9KT2m[?`Z LߪF}B#MeZYИWw8n 2 '߬/$;*՚8>튫/_: ~3Ml$/K14Cd=o*m;jWXgR zkTIљ$D{%36bFRrr4(GAQyot@@',(eePPYo'*t[`zQ]$(⬷H5q;!ϐzisy$:5 .x HuPk9={DnՈ.|j4a dͷ}T=Ĝn3j5Bp0}r;?IϩQ4D}YO[Ȥd,‰d]d<:7o||I!by]i\ w&J< tm\6`#d2˕6BvN4pn_6@ ]Q`h,#>' Sh)I4Z~( sZ-JP楢x(h S6w }6mqnF{VѨ7Af@TZ'j2֣3rj *7z+ HFȍ 2o|w+ :V7xGcTn-9DSzL8y 3g80[t7xkvo+t,ӱP5|l'?PZ<7'M;tjzηYugCq'ۍObq\FGj&+a V|hV'ħ?[v?[OhW˭ j 9x4^.Þ5 ʲp/BQGx}.{^t"P {E1AK;G@?SBZ-; " E3r\c@0A 7+O#Wxx˩ !u.N'#^{ߏ'=wBl+I!1Ws8 ֦otقpW6h%Ca1D IgV'''"ډFPCzh22Pc1:Cl׺&LGnMu0٘|=ƷIWgm꯿2ie7L 3տ_HpF_ cw%6F>k_(5I#7ko|>+0>9kšBuzK'-z;l~#H |i65\7r] $qoⱄ360?<ϦFѿ'v59{%|tE'Zf3IvfzWȞK; bӘJ z-_:β>>Q7cnq>./b4y#a\'Gӫ5bFtvw6\aKot"5T;P?`anI5n˭%Ɩ- zschƶ|p/{rx_VPlӯ|^g U-[I4vg*e2uܙrvO%;]sp/R_bpseFd<|9Fn? l*&_.Qz`&?Io9],>q͈N= "0ryhLcw2Cr ruS^"# NV*1sL'5Y<L6ͦ'czD3^L̀k4ʦ5MmD4?~U_o*o6;emI?ܛݩXD1de~a5>Es0G‹s޽6׏_Ը0gJZ/:0na߶GyIN1y)P=t0^ͫZP{bn,6flqNzm-$lw7>YҷŪ% r{^K3ղƖ-qeތǕ-7""ϫ~~z|]J+U*Mگt{UqWu&zsW/V<_*}Zms>)^X۲սXnn-fM57[+|qy`}M k׬Qn3 >|p-\#mIqaVVy.lM'j{l]b>g"v^gn۴v`gZ>{.bOPm 쵗؍=qoq>byбoXB6Ć֑FЊastAqUv̒&Kf=9;)wɕ1}7Κo:p>SmTrNbM.kٞ-.53SWlˮq9|sA N^]et~=wo=陻]yXY [szKCe kٓd'jc֛@ZUuKy߬E{v75O7?0bU{G⚠-S&mB>mwG%GJN?m8dKT%G=2$Z~M23JGQqWbƂf; 5.Y_CWqd*qae۔^s663-3|0jpO]@͙TW|y\r%o gIm7sدУs0,r`ro`tq-j;xϯT >"I)TTj eס{vM:&~WI3FJeI岑tھ4ӯȠ]G]}M8,xsr] k<1N]t̓+ct5v[rb ~l5Yn&v~ʵn֓Ym7VS|0i>OG%>q~=LvdiEoɜ%Z֔ET9jlԨkv4WݤU/'ҫJ#&dܿk?7و^uəh. [{+/s'BT>][OL,?w> f)VH8~ʭu[ -b9~-[XQ}ޔOot!Зm\?*ya-7 Wyz8IS$S|&#[71Mvr{;~IW}l~#y~e5Zj]}ޙ^G`vi :П\ņho^\5Mw3ؼnŽjc;ݻ-}jȴx8Vil϶3`.Q,_?nGcZ?߯} >Sx\yjz˧yY~&Gsx{b/hN^^zثgFnýFuٜqoLUMO 3:v9}{WK&jwFwW"ZjWo k!R;`=>[!S$Z)9"ꏪh:4E> [cY"2l^Ogdrw~,n}~~> i6L[7|]p#s<6_77*[\!"c'_}?]z8vY^QDղ5:﫥T~cvk^hp8YJjKi\d`1fj6L7$saߜ^Tmv>xM׹80&Þt}+>a-}.Van{%l 6􇇸G|goJMkQqߕ̰IGʃGǥ0ۦ?_̓Myَ1ֲ˦vsfo_X#S K򵴁Jۅ_kپPhdJzWײqxvЧwԘDVNzQLtAk-cSiΌc2Izrə]_vrw#1lO,;Z!NdYO7- grm')旬D^09js~ek(?*2 u'b;Z|*@?^@Zv嶝}&1B2RP#i?ZƴAPpm焔|㷸H; xz>o^&JgjϘ4'2DbBH'9v<`Ae8R>#SGG%<~(Zq-Ler̳&xzcJ䐌5V%* ɘWYUɿ#w!F.sL@C*O;##PBMTאH A RLd~bbR)a;٪  )tiI v/X$꣏`$"'5gu- 8Xo^g;ۦ+!j&wMJNHe$mwaŁタ vKB32K)s>kFS\䩜Mӫ"r\ .t拑BIvqhl wȟβu%u!_j*YC>]eyaJ .ҿ'b=]\:HD}~ٽuTk+I0/}ӏ#]_nOjoӔY? .`hh|fӟNj#[ 4s\}Dy6f7Ek?{GjOݏ)$ s%,@4tjyL~J[PD [3, K)}B#7p80Fp]FK!FV)Kjb cNGW̠VF L[rJEMDg0)ytv@ħ!#+&Ewsߴ_"&>M򾫉Fn ]S{ScG ˍc7Or*@yBM"{Ƚ'IQ92>#ψKl޽vV&0.Yv9r߲)J3KK3KPl–1I 89"tr_]=%ݖ%қ,wƘ0/1K/ɱ7QM֜] 0wHO?#znr騠A2Tr^9LMbGu;J+&Q?d]C +מG6n%8}ۇ{jXoTԬ֬Cws]X3eeϙ~mTKBf:2sm+,Ǒ_;koh[p3vHE1e8(/NdWb_s3<<@sK{oy`k^}yeȩxD޾"oٜLXBЄvh^%[4&uɛ&BOljNtL΂ Ay&pn'LaOD 4JތeD+x8k7C.ujCV/.$ӦGf1 M9n'XD0Z+_ ܡPg1A}Vrϛ,ش]5sd>} Tpd8|/ʴp Gf);6Dְ$B.dtu>ֶ#9n<䑮YfAJx>H$uz+\3= gkb laP죥m.F5 9rdÉYж4FXBwf=?hZGԗ.)Жrj8/zYCB{ Q 9;MP-r8kroU*_}: DhrkɦkFֺb@q:dQpkx aI%_>~9UFJ@ },ciq$3M)RI,by3}Ԅ(')稩i/P\J,U4rғTbħ3^@sEKΚ{ Lwrf?~]Fd6^Ik X2rzXp+^l C# nlc>G<&5۫((BIƼzbAfFo5E5$SW_T Ҧ\ZK4vԱA Ӆdf+9 glb i{u(V--yvM[5}/sMbEx70ɺ3Q&1p+Zd>?m&]"%19n2wA2g-. ÑEˠ~(1αk &rqZV`7vm $\9)Uݖ(< ~3|eߨ91z$e0)QHy:X3Gzd[Ymivf> qq7wB;48L7.f CW>7sT|J̇ >;&zgýW|}faSbA54ǽ rOKLDKY{+)aJ2x..x 25~&jU-A"Ct?՝]3Ө٧'qH|#lSW\j S5lz˒wqؽ"Qt&k(z56:3'ά hKy"PY},/T2#'5 >KYL,/XULuX>Yc 'GFA{v}Tt+Qԝc隽 t@|W!xEQ̅}P"=ZqH5-deo(=9G[f#َVeO3Tmy?祐q֎V2\b"]>(p۪/ d9k6?߇E |Bm2gv׹ن&kYG޼D3u @v0{@v$"}[d㕂x1@_ krv$j,ݫG{ ѹI251=A^ܮW.yR.b^eN$-`W\.+T#5Kbm#Dž:iGg*γZu!`^Y(T;S%q0qݝ0pr})~FVaw C9X[&jnuPY.z9X!+S=DjEl[wx 'hx!$%:R?H>2[?KuWZJfZ * m\nKȎˍx??ﹻҸA?x9>;wrwfsN[ %ۿ/KEx9v/g?%z@0|5ͮ?Fi|ʡH얣r.`}ݑz2 䉣sO!C6YB)k{ ~L \݊(zGt|aznlFK6Hk ~]>E +Nf1CeId]`_T+ܱ;i]o[}0pD6rӼGclrGQo_XM} < 6DO܂WhEkwSJzSGKS,=W 6W{iEQ7|͈1-ҳY#e,~Ws:~pR=Pyr}t9sr&ہl(7Z@߀bqV4rUoSM h2@Jyci=ԁEp깨3@ ꆩy| ԜW\y6P)Xb5 -Dt4 =߃P.+Y#e~xN}LrPfya=b&jjl֖ + ;KcJ+4,@͎1P(޽VU :R)k|85H0Dt]S%9;YP"5*ay;˓޶ jPn:"y +6X3/Ӭ S(B4:i~1F (Q(Җ2hyi}#jChTkP6*46V cJghN m{.cmۉ&?Z Zo(@Ct{ m4E xDA04]U=sE4<@ `ԁV Ի*w c2ki֦HhTm^hKv%\`1د7_c`y5wx cImI@H3+q+TBCnEz#2,;VV}hv;¨ސ%Ylꁾ/@D}8i~~>]hRxθEZ+PaGV.go국5G|Xkr<,>o)ry:xk?fW{\VwOV(txzD11$n֗Uͧ뭇ZQE'/RЈGj}Uyʾ8Sy>O_1̃ooCͧ`c1Hj&C 6F/u(FVͧaj|jSig}D1U%6DJ!5S6!J+O|xfzJb;mwl;[{*7b?X1IlO='L9.}k3eּHc}X8Q+2#Fj@AIM8Z.(y=}| 'HCI^RLJJZlP,Kˀe@ٮOb_(ăk}hsWX\)Z@'#m rVBO=O^]rEhI=pB-u(g:L,{HQB+^1OוڇwP;[ M@Ԁӝ\nnyS 2B1|YϿsx'X f[Mqi Q X?cs0J~Ȕ.>Qb|`_H|ПSg7} "lKc!2$j3H ?s 0f>ՙ!f_MNs9+3 9 K(&|g;CR>A,ɼ\rՓ9Q?s6=kfN12(.HVbAW<{ک`|PdI]qJ%S׬6#<1]Egh 9mXȵg1QJ6aU=*$9)P6.ؗc&#x>ad9Xi>w!I6ъ_:Q,*j| vg sne&_K.Nӗ;?+#28OOPI7G:p?/ynGh*vJs=`L )ʦ$w OVU>|#]ϬCw5=FƀN,I{)1\R+"54kC(!NVV{pxwNӐ'_3xG?̯YI1(F&QL0y׏<1w3QɅJ> T5CǶ7&T~Or{sufcUy,:uL:İ۹7*59!ɆxȻ8 "'Wo (|o!"TaDl5os*&*SXU =8k(/>jZY CkQ@n4mZ< '̲a^i5+@IZ _9>n(paXD\@eb7dqq*ڝNdpd^7r UqͅLɪ'Rj^{8 M&Z 5DfY7NR~k@/Zgt>Ek sa=AQd A> PG'c d+?vW%D13}+Mܻ-'4SJ:kR'͠,x۪yC \^V%lETQy`ր&OK\X30M&2w i_td8bėEUwoRwaXv#$yKT>uVYtc"b*FOJ ~ JZ/T^޾f 't=52Co|HThPgcC)C\$ecFJy:}zkޕNRNћS$b'QJYd[i"l =M'6pgƚ8,E"B~:9sJDWO2b'2WrNќ۬ʮGmHܬ[FZUkn~]H څ Zn'/ b=+]8a5NNko1ȷ`VDtbrC!zU2$^ӋE=KI28;x@,FgE4Fip;AՂՁ]{O~t7U+ A0P&Fq!BɢaY'`pbPSݰ|r51oyFg ht?u 9x!kӭ9b:K!SL"}=y\mclktqZNсʺF,i9_WUo{y`O\MKkڡVǻtfZ,D !aOO6:urF]Pi?MC!*JyZȃTsA?Re󳀽H+]s8pqrs@Osdy֪?v H|EN['t:T]XK|2zRed(2RgedtPR:hDI }YӪ5_柦-EId%܅0iq *CSM}Nm*dzP5^W{p(Jpz8pS5 j|Nc/G'}teey߭Ho~kO!췖Fw=t'~xGx??ԏg:*N:X 7"0&i>в](>f)mfuOD8SOf;,b˷|qGyl |?$8>0Ӿ%ImYjrVe9}c{uU͞K|ƞ+ |ro$J֒~&=r¥oEsi\*}K47Oq%7Om2M(&F%r4j:%(yX8&( ^P8Oe թ9C`a=4T@`ɅAe&]rRe` ݓ# $u G1Ka=c\KqN*S]7 ]-kT{[ wB{Wo,^~ϙ j;p?k}(8UpB UJDbP Opg7)k I7}?;T['2zS7ioS0o6~7N5GOucc+mc7G&//mGFz~TG/B/j٬3\<ʍz=vt]Bߩb>Ct^j@ aTϚ)b[:k|9`YJSk-c'lyψ+5YEL]~~$JSɇGUyCᦼ߫q}Tp=Dָ&f%+6¼h׸*wm|h*+lk33U*Nq[n1[eHGz-fFo:ycůh6JNl05I:T7ԥ ZS>Uv?E<3il40>Y ;(v|(Nl;oɋ>WPVѝ)8 9G9Y-2nWך Ĭ'{أgxGj%mdqze4VCh[G6T0F@y|V\VeZuڐv#gEG%wڻKdՎ—.g i'_nso N]PVo5tFd2[B~P~IQNO)/W`gQNz01)A)-Oz3F`g[S6ݣ(_g|ũok,ϊf~(Wu(}=4|=p?Z/ȏ_oR}|g͗wC\ׯU8cwC}]oFYׯVOn]Uu7TL]^+׿ <ЖžUboiF]QOrnE$DUo _Xa~PN]_W3uKkToW<~q{{]|]oR~={b5b]o_H{K_sOp%q~7\'”TO{ӷ(?x]$s#!@uV▭(iĺ~)_\A"3uz1u7ofEwPwW~=ak7FZ!!_Wh_FPfĺ~&uДt\]nouWz|_h٬huVO*`csߨ04z]۫K [ׯUHu2|]UF[Sl#;LwŢD_6?h]n0ZկY!LlD$=wEIe]b;o;fڟq!zo(ukuqD$;ڷke<}[ n#-;R,&{/g[\v.GsX6'-CGlHmE[)&?+ˇ)<}@r5/KǙT6RS3~Nr4ߢNƫΪneUkl5)ܰwNl]߲ vp7~5 rz'..tr0z_:Z/kϙ?^w>w/[%k*vj5~pWxnhlL]8c{n Ti{i傃TyV)\Rbz~}G1b=]tSGcd]Vf`%ɁOvfo -XuV_ZY>Uߐc< 0/ *r*gib6| #eNmZ׼iU ~XUj͙p\*rV,jE¿!WT^"#s7BŅ ^-m,SJ?tOZG^\MkrD,4O򫾰׭D:h!M"V%!>e6f}/'ܧ|=Ջ:~˕Xs@{bz!TN +ūə\OwmGG~(XMWϴ(SDvTsoe;يQqYD8"<2_כ(_VZN QrJ? ojvdQ)?]_<|nȋPRydIK/;3ۼ)ˏθQ#$ީ45:V8҂Not0E=C'B>+- ̗騣 NW55bGGG[KAH0 9fZAlOΞrJfHLЁ,7t!hTX1;}y>b(GbE|||9sː$ Dn^TZ{b1  xҷ mDʲT~o6t"ru7O=y--{C_d&6ۛ I m 7a>W莡-'cQy,;:GŁcylz&6PX69Iٛ؈Qk"ݓ}NI+Da(j, 5S{.la8ɠ!Y39Ddm86 jQBGAjK1*S ?G - M?;$Xhw̾0ޛ$2ۻ2_YW!/f;*\daa"bDs>Mȗv{ 3'օvT6j嫻_TV' ^j5*!=W<`ӥo~Fq9LKD25%̀70)ȫռHܴeJ,ϪEu>>yYP?eva.A"$eKk =0'F֋cAv#E$l7 [D7T^ZI~})WN¼] 1UVmMXeje-~S۔foNN7W\QUIlfnN4 N8L<+y7 ``0~BAһVE^qKlEZl*KmFf_Dd7'KO4ߥ>17wrۇDa>j]OjD+'k*#Vn6Մ}}\yԉfyZ8vw6U.,"~Re_r _'|giEũiOSU?mXpXRUCS&.J4'5U,ȅ\h]cYoW?W hT.df|u5#ᕋ9b^-Uj׿ibX"V~ͽ Ӽ6R.jYGڊ b df赽*Pdiu_|R-%lI5ylmlKl5cIvkkҼym&[Ҏ\2e^tɇ+Շv\LOQHS=M򢺛X%$e&RUZ]f)0eܯ^;I{kߡ\* ]ZqxMwn~,k[ /VϕjheR>_96ʷcŏ_٪;U9\/fOϥǃ3kr֮HE CP/z1>D#X>F_+j;kmxQg`6y]\C>3 T V%!k ;u{m.Gfž4*Z)S\]5"۶lV+jh);  )WU.,mҝ{ k$7[@[.^nQHC;ЃEZ#9,ѣNÄrb %ҁvεS}2ID=ǟC­lW/+9'[Vd8T |x(q$z1ׂ=53"J΅wXy Uwb|kVzx.),jWV rFXΕ`m)+k5d۴͛xs ;cOZ=ٟ9b{ibrhՂRR}T i1V˴qZib ߥXdŰ,tg~nH0p>}Pî&"T9Nk4"\k޲G( M۽t2D>r'$ڥw-#Bg{$eu2ZQ,J(#bkP:8$/tj7=hF0/M3 gǢd:{n'*m5zŠjM@\9KkbI/m;Sߴ9 825KaT}ǚC5cjd;xǎ_FQhljR0~!=|6ǍH(8ț|6J|Rj~a<ϟ(hu;>Fd|k,&RSwM\_l܁u+J|KkA)q>~rBڡNg*f|丿RO"Du} #͜N~=J^ѦreF>))-z!nG,r*,V0zs EJ*Vɇ1Mus C*n^52ާMyP$e֜T{ 3~8°7N)r\5=<2HࡑGF %?uh瑑:wd$C#=$?xh瑑Oyd4ɨFzI䡑GFPھAcYKΔ:QGJ[/ޕۚ 4ջ\4U"4ȪLʏ391ӊ'5yv1;N5tIַZ55L/~+-2X#u{k}TFF`cm8 l)XaSOՂi/@*X+TZf Zc[J`i1Qy.d&i4?l : bniZ+n!0!c=q &\un)bK_/'i$ד,yK$8741 [I,bcZqn'1MwÖ0ɸə8w6wmaYI tf[ bpuu0L qp\qބ LẫDҵ5 /-d̵ ; usW pdId9A?#5C0\3AP!LjEYL GPsWeU?َlئl~tSceF`8:vL//E0Wu c:n ǃ6 P~a\W0fc m~0b.F QSf3LarGѾp] oe?.,!X#g,l"Xeジ‡q&غA0q꘹ c6| !/U| ]* di"%4v v`PtBF}licB-a;:`\R~ " BK,qOJV`! %%Dg?/Zq !\.$v%oc@= KGWDLA@*u6.c`l0 }0G>7t)WKVm^AMJAz!UI 噴)S ׷7?:@u1 3 E' 63uM3)N 3:Ͷ|D`lla-@C"8_qB@@0c 626:`m[F~;u- 塚VR^R`ڄav=%a`Il#2]&&XX61T.c[:7'ݭ Ux7&_1Ol6F1;D umsHV6Ar XK`u#w ,5ԬVk/x׍ ô]n}<4Tl29\T (ux)0%nW#Lf̏e}uGr$}& Õ$:TO7 $;=H>3FHgBK Beŀ^IaaWW BdA=vrpPa9\~$Hՠ71 &ؖ\#LGIz)9) H9F#"c „$c!RwPXAB0x uElXVĞi0&/GYJof T Vh#(eNat7VLCJ 3d+P|`JKRW[ᢢ>Hp0(LfnqS?0$u4pPBoZ ,TT E$#8V8r P> endobj 27 0 obj <> endobj 36 0 obj [/View/Design] endobj 37 0 obj <>>> endobj 15 0 obj [/View/Design] endobj 16 0 obj <>>> endobj 49 0 obj [48 0 R] endobj 68 0 obj <> endobj xref 0 69 0000000004 65535 f 0000000016 00000 n 0000000173 00000 n 0000044768 00000 n 0000000006 00000 f 0000364745 00000 n 0000000008 00000 f 0000044819 00000 n 0000000009 00000 f 0000000010 00000 f 0000000011 00000 f 0000000012 00000 f 0000000013 00000 f 0000000014 00000 f 0000000017 00000 f 0000365002 00000 n 0000365033 00000 n 0000000018 00000 f 0000000019 00000 f 0000000020 00000 f 0000000021 00000 f 0000000022 00000 f 0000000023 00000 f 0000000024 00000 f 0000000025 00000 f 0000000026 00000 f 0000000000 00000 f 0000364815 00000 n 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000364886 00000 n 0000364917 00000 n 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000000000 00000 f 0000047136 00000 n 0000365118 00000 n 0000045182 00000 n 0000050119 00000 n 0000047436 00000 n 0000047323 00000 n 0000046131 00000 n 0000046575 00000 n 0000046623 00000 n 0000047207 00000 n 0000047238 00000 n 0000047471 00000 n 0000050193 00000 n 0000050455 00000 n 0000051579 00000 n 0000058101 00000 n 0000123689 00000 n 0000189277 00000 n 0000254865 00000 n 0000320453 00000 n 0000365143 00000 n trailer <<07DD183D0A6845D89EFA3C7651D1C934>]>> startxref 365382 %%EOFgot-11.8.5/media/logo.png000066400000000000000000000056441424347352000151410ustar00rootroot00000000000000PNG  IHDRacPLTECޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤCޤtRNS@` 0pP0Ͽ@`p PߟB IDATx^ 0@-  QPA*%L b_{TXѢ|(#?Q#:#:#PzzwvC~{.J Y"FO))E)) '&)mWؙ ~ZD. P |uz~BII$m򤰕BH59%?N jpʸ_f }x]AC1#"iXC'c@ө1Jnyr{ y\thE7g?׽{#Vϲsi֚OR.NN|t%oOx2$z7nr7 |K]+G\YÉ.91iy>黭z|\9eFJ +rJ'vIsH9N~EYt]žoOlPӅj{u6s+ XB]w ]+=o1zY䡃]$)o@[';8ygBȹǣ"e@G/LⅎFCpjyT<9áԅWҒ&u ~7z̘i*l )PS 2- 39UHhdVt>ry@SݢtHyN+;EW sg/ =G>28:@Wv :u'dU%!tF7U7;Gcw#p8v$:5NM.LȪZ㱦CQ=8 XFѱkvq71k Y tuL%\]D "Xd(l}.KX>4ttT]VCЙT訍 {)tPmu 㪱}t^a b_j]ח)Wj}FWe:Y]m9}ItiC nvK\]t=FGFQUwgV1*T=`~o\a=ac~w-G̤=ʣUt &td+i]t[@E7HZqt]OXc^;(ggcQ͢׷c~=ۜUtYݒOAǭMtj,(Y\Ms;з VE8p#oA{&th'_/;V6:-#CmvyB-9Ћ߯P߀?T[OWo@ߟ{6I7ϗϽx:A4}{ÔH::IdsF7{,zԁN6gitzŴ[DuBWu zgtt:ͯ54R= ;kN9}kV4C׌t9p:I@'g::A%IUẵZ }:A_ <9t ]b"fg\9t|;tyOhY5LQuq#к*t5t9R}!.OS: KS_|2ǘSmc>Ԅ/_4Nj E Ak @'cE׃>bQT`%}jS }B߀N3a[ET:փAs'%U ::>Y;:ijB0)ûaEM3w;6yvdAGlum}CMQH_vЪ"@km9~5TQMabkB7FݶVC訊M=~CP3ёUڨ#ytHSE6QnDs$gs앫Ag,p91;@ǧAGs:|1GGP : %%Б"tG|s=,NtLGaB5| P SՠcKnqb=LX/#tKups&:zBg2Y֕HȪ$ HZŬyTX֕K8 ]iePpcɱYP?~xhF:3h+hu0tΆՃ& ;%:<i>iqPPػ|%u-5}b ;=ZvA#ܲQֿ1m)$4&mׯvꌰu m¼_tziқ"=Ubun"m0S|p6z1l"]*5 HөN$,CzY !v,Bv+O}j78O~B̌Ѓ @mŅm7™'{N4Xw+1#W:bC*_yPthѣOi~ZR:} vxH 2B[ʉ ` R EH8|N >@Fn䯡Ϟ;\`$qqm63j6Qe_ߤٗ0䤖DuJ~ud_=rSUf|V<'|M,N1fŊXt<@ ^ F5 >KXs[R*vzX9([?vaSフGw& gMIENDB`got-11.8.5/media/logo.sketch000066400000000000000000000431331424347352000156310ustar00rootroot00000000000000PK#\0s document.jsonR[o0/}27얱7gLc)Ԛh }GmYskNz R͆Hm!|J30Nix%qL (<ȃ 1 *URUZ$J*t6 +-X7b5Y1tوb-J #ǭI[_.z>=%v_웅wo3.UZChz Uu yxS\uGV|6Yk9x~k <^o@e4[#Ǵ0Msϣ~1&CG 0S?fPKKm /E/pages/FDFEDD00-27E4-4413-9844-5AE97AFC2504.json[oGgoHc"hq"qMI9vj)gqXkS]]uꜚ_Ww=(zgg.&dTĩ;m?'~8q8oכ׻ [|z'g:_,wwbu%,r}Z,_:_mg_7ŝ6̜ayj/o2`e|wY,/.wVabwrwr;[-?כ|upS;w[*G~Zn/WLs) nM^ϯXwE9Us6U?Ζomv#6\}Ѧ~}5+lbR7˝,j1ov>/jyf}sqyG#^ѧT >Th4$ lVD3G[|rr^Wu*wqЛa/էg7(n]%^Gsz× zspj}գ_u<uq?:F!I܃3GrGZ6 !ؤe>UcmZ1*@:XՇQ﯋^ɥ,uz5g ;_֛;+r^.FF]V:πRT>%cN܅0DUNf}|_C[/3|]q]ޕ;2jvxJX\}>'`jLv:Y&0XMS= S=5{_0'%zU̯~.6(!Dz5kXlGr}bXn܋͛(isUlGˁʻ͚=LlLڐϞ1io\6!@s߯UX ɘX_*WIߟu7"ih!@|Zc&j]& ޜrW5;z*FpM ^@ fLFZdwS}>eB:F`]:k"AE#TAY $]n76 :8 Qd'ksXyodʉ}^E+60C)wBrBN\ ;Д豶噘2AtĈn-]DYd iIPiM>T(3 ˦QF6T_XOlo]Y2D:2VB9+-nM'gQbB[u~ՂkYUa$NBܧ FIOcMx'/g٩lvoL>J+_uMLxlig7M;?GM7HܡiJ[ߩil6qi?OҬn(xƉ2V,ATmjc(kQ$>!UE_o=/S1.e3{f"B+6U_CD"a@%)!8J[&OSIV4;T&d+  ĵU MŔe %lq*&lL,-)@ BEX u"4g!!spLslw lX~)gO%O%dTĂjֶhXR,(y+߮\b &VI|B-z>Щ0T VˢDG*_KSrpX`&8SȞ&* ^ɝlIY٣zd(sM,긹c>iɉT!EB sF !ú"6x" Ќ.0&29E*$<;hS>{`ajy lf e")D$DZ4#N.P=*yƀOK<d! p3d@|W:uIaA4#q#`Q) kkOI$?yҮ|e'QKɌgͱݐ_y4q}?3Ǔ<_sK }#hH;#s. 9s ʾأrRBt2d7:/d;iŢ](kSG@-yh=:&|ޠ^Ԟ貪N!)FKbčSaHv.P&D\m ڤm8SyQ )j7[aC 5JrJHd!( ո@Dg8(sk0UƯP֘Lt u̻e ^6Q4@^FT[~xN>Y5O#/Kw]iAh}"dR\,Ē {M"4ޗPI#-1e9w-M#%#;4Q)%T uI0􈗞NPcl$չԢ>#X#(= 2JcmLLF`kKޓlՋ&A4IҮfoX䇉U pY|HaZ .TL;,O{(y)kϔ=ck(bYjoDӳ9x<컱Øt4"pv<9zY0OS%߄ŗh9%ǻr,;7 x9}]߅V90$iG?nM# eŢ)NC3Ң1t :咦d 'J/D4 QjMT\e@F9ԑ;Iyȝi߆hfyxҲbSvEKK?6EUj,To!Mө`9 NP9br]ؙ6[p/]eu_hnͷ Zž!Tm)ۘjF_hl>5FWNyNwi@f2RU|[ pq\%U~`Ϯ a7_{lPKq} user.json˻ _1wKK`T$4},]]Or2xLnޡɰ ö\p, ]'' Wu_۸ka2 NOJsJ"Բ. PK;~ meta.jsonOk0ƿY%jBd*;]F6&"~6:}~ϟ vt@kDSYO; ,M%K*q{LqOQFW0]Ⴔu'TuUHLH|EHL(&ΈA]` 2sym Z8PoM.Z"h:\^>Uc Dڃ0'M/6`8!ڛhc)a W.DqM?%e~.5]`on'PK 33previews/preview.png3n̉PNG  IHDR=xsRGB3KIDATx}wtǙGHd09 )H%+ZѲdr{w]cޅ]zo}{Zd[E)Ss "g58 0U3=왮_Wq#Xh_W4nInsRFca6ox oYHp̀\7d`7 p!l3[Þ瓌`Q>[@@Wg4='?" ȏ}  z~`A&ao0:0h5#`ƇKF`,BEr#0jRF *Xl|`g~`;9t`YByg,'V! *d^F`%̀p!#n|`  h~`;fAnS`䔑vx f?NE0X0s! N|#002P #3 } F` `d\%#: >̀1+Ǟc7f@s1#0@#5XX3 +P:F`t` _DaG? QWWu⯳qB87~|"eNJ~,,ԷxutҕvF-j(33_ff*e ƔB &RrrbTMDmnnd6RcS7n 3h,YE3q /Gjm^lK@?|榈[lṶLO3Ğo$%&PJJ( >ph:7 T\ڠB.C;|J{:v`H3C?7S*~bCʚ:@4D;_;UQJMO4wN.%&_iĮo.٢:v[ěm iiɴ|dڶuMDd@X9WK%(xޣMI+OeK&SzzLhkjjj W,=g6[\Ѣ=_k/ڌynMȑ6os.79<3kָќ/#ǪXRQ3;GcF(qb  H&&Μ&d7iحǸwH055]Cc:SC}}0?Jvli)oQ}}gDM!Kdʕ6eD><}U)^96n cO@J0`ˀ %M|ed e@Nb1gaQE==aApNȿ7kf6~\bFd2@ ֶksMMz#^B\h?x޺tۦ9v:eހ;nc8О59!|u̠IShڴIFUZFF2M̘0'I:t8h& 0 oǘ jk Q^l.m!?y69;h|voOms {^vAa%q-vPJx~X؁`"F%99I(siZ8?/F.3&+4{V=xa3n>{Si&9bwRa` t/拂 /FX8R;YBכ / :5C/1)VV=XW.Wha>]5ͱ9b?j NjذTT^LAkaTb4? S8=m3 r-9V 5J\g~mF [oA1f#`%v=DB0X#zarp8n!qߵklQ&3*/ЅzzerTow;‹6FshtЍ}S =#":6AZtxt&rtj?.GhC -"8X|DzD̵͛fk/p#FqE1|M1ʈ88kG w{=evd$N$`v~|<-V$"8_NNT֣tϝ  MWR RW/u'k? t˭sŽ7OU(v콬0"(;H0.ݟx}zτa v;9+"8~"ƽw-MmXԜ8uUW ,:FW.qc@;)1 [$F@0']A ]ڎ+FqWO)'8A=,g1]jj]>C> ne6},D# VX;٢I zǛ4I;BNbR :Gч³Jc=B^>Jw-V^&xE0=EyxcSE-)8j`G{ #t8{`D8V1ɣMd>Pu,`eS|p=^opTw.zYX66i;z-h zs9# .7k/E#9p}egQI,Y}/87aY]tlry J_*;ε=gч nrx"&mbg LUXB2Q¿j$;_{j';R3@ܴMxHiHHgF;liS')]:. ?yTY€KN뚂BC5J;*RA,sb=3W.s|9􇯾~R ~,p;'1q(ǂ.yrjwY9T9?2'%!vC)=tLQ1!='\`S<rl8P)l`Ai&\*` "&FlLe@eyӺSLDJEP@<8UVE$~kZW^;d060;v9U7kVozQHUGw9*Lc@oP㥧"ZJu\F`ȧqC*jiV; Fo񅏘Ճ=V83SOuSUJ%$S2SL(ȕ"DDG8?XզǠdy>+0ߗQ~^`>~zfycnsJE~r>৪0!$^PQ8J\~Rׂϛ;yEnkd6gT ϊjle9༓d﫱nfzM3<#@  / kauq 3JїW0w'! eVzjXIF`:ϊ$ XOndAD R5I"C@_,l| 2BFOD"VQH'Va̼N.lyF0ΏlyT%!;́b QDFqff)joF+gD[f͋*.u/2X>:Ȕ$|}I49ѬJn`r5t`T sN,p(29T%FKKU,~Dŀ"]w1P!RO@UU8-~@zm}2z_߀ ]{юAp"Du?\f$ ?䃒 P9=#eP逮dp3υ3*4  C^g GJH.HϨ0WiEKLP: x|j Iˬ݋'Aq[n-}}XZ 3 -׌H﫢m" wS!e ޾֓NH !6[%*!<#` {䉴Igb eVjTi $ B}!(v{0Ā`t,&&>*!枏FUs}wG'/ *TYR@ >! `i,zn˥;'Q )&4|]Fa e=tw@xpH(c ``r-gPRRxdF}@Bd!ˀ࡮G5Ѓ=P8`~A| yёcͻ >aPd@@V 'sϚUVK;#p.5Iݸ~}^ ]DH nL$,Ӌ榲8b3{wB7yc8|J.P<+e@uĕЫ!4 ?*@\-x[<[n53D-;0 ª;)n3*hN2H^"HkEDS,bey 9. ۊzagS1`> ~ZكOM jlꢊVaxEt-A]v  5BkY?z~^̔;YծsbD 6`i1>olssӕ܊swrI[gˀTA"-LHEAzWw?^IH'Q%{!6eTUC PA;ǜX!H~!4ϼ9rlcbF#0fJ]Px]9gJJ %G3y fS8荎r/LC`,*X؛U PrQ΀T+1 #PJc^)Z|.8shep!NJ[I^@Zhy|0~Ȅfb8€TJ?k WZɐ}BT_ ?q"`|**? * EFH>aBeaN.ȓ^p01Fv@CCשJ<-x"&P`T {d k7d@7\\D*Fvu@+]."5yzI˕d ,pbloq 5}&-J/8 :}YNE tGllQN|;X~^Wc@H+#[+GEX:(W]}#j;OOg-V⻲kЅoVD&w=7qix*Dpmm85Z d߆Q)D 8&F Tt22#е/a>auz CaC}EpW:||XcQjmEj+V{MtZk*e&.5= +~ .RۘwWwRTmcKJJ XʨG1Gdy!1 ي!x{nud MI&dy6N0tn$Pe;{|֮h!}Kbi~W4E%缅攌R,U"\a{ c%2d9_ߦ1^TdnxT3jO|XD 'T)&>Dp*X[2E2!YPY_`>_R`ڹٚ( U;{'GyOtMډ$$-M葂:}ZŎ TB8uk ҕAsAab NzvҾf]>ثON '$ŭ}9l %&*'i-;=iΞeM,o]bZ Jz.kV kXɄ촂 C.T[Ϥ L%$͢-2W! < ~$G;./.i+,fBe2h1BV^LY]vL=E¿/jd; E$wZ5G$%>e@^?$znTW82j>ffmdOǺ!  ؖyⴇ $Ū&:bf{6WȨx3TryڢZ g&N2 StS36m'j* `_F\e1Ogo3Bq&qn%Š.Rͩ)0!0LKsvmɗuzyn T4oB6)w$|4^k?^؏I^o? c[P( ϭ_|i[U3 a -,&UfU_%QSW% q-sJWS T+~!R=RxeByZn*˭VE#u5CX}rHPjke)}֙,E¹!RX R9sa Z 9q@ce#{HjU',Y|/8ǵGYY)Af'aBojLDʹ|ya? ||RqZ~|j^,O)}1ADQ[:aNcBE蘐 2e]@lײ5'Tc^f~nU'eOtjuϾaBo Q2!3qE_D{z|>gMoT}|#!`PU +{~RpB Kunz= &J )4mJrg;e:GU4dt=h(2j{w9UW騎 T~B`Bg٫k[WM K|jM_cZD,ǽ`@-qm \@NNjky3w ҇,0ƄiΪ?5h+4OȣǙR |!Aw1Qv|ny!^W>N;¿NBaʍv߶T2\k(WZb>KSclU>nvx "4# a1Wέ]PDDӧMRƉd f Eۿߵߵҽ}YJ> խ Tc;ib MhM!Zƀ:^*j \HJJS&JVuŹs(z )xA^iO"@0m0nUUǶP3@§= ĨTL(c]՜v(e#LBL&cBш~%/.vWz:u2`>)d>h{bw;ƅ#G`,̒; )4\@`tyGGn2"5BMG !hm;IԻԖv1*- ڊNfECc 击9_\NaB6IFh:Lz.m*`>e>h 1>Cxp1>WT'-F 9;+5\vyw)#Lz߇0!=nR)Fi nF~"G.tDt.2ʖ6lQ KȊ҆)0„^ÄTtԐHsnv\_tdCt䭎g>^1">!?OtdDhjFWk)U E`âJdBOF„T0DQۛ[[h {"UYw*]mڋ@>S(wA>m$RQl_Lr2B.:e#7**gdh BΩV) ƈ22.**[ 7&Jڥ]Ҳ@_N\A;:{Ie- |\dʚ0͑c򗵡p&m6GUo ]`8}G`H(,pqEsJe we66YnMAY5;GWKo ѾF.<czZ^.ofcO ;rTi׮ѩ3WN.0 ׂ(Q^ cz2j.>D ,Zz);XC˱ pMGDI%eݚ4~3I'Ml[f} 84t]X C`z½Z mv-0H KA W!6*p5U+#`t?/OzyyE 6JP /.ơ**[_K'F2 \qr4D,?0Fشa/vvn&#LcRFV2whLɀM9eX11F3;L'kTLcsh5\2g}|_(زyϿ,!5\#p_|fo 9ᘐL{Y:v\e@Gu dp̛C uA/8@vڹ`&||zz~?+O!83E[I݋~A{]N|w.$XI"jEjg}N<& 6 +-B'w? t'BiZ!w&C(;p*AqDE8Jw?8GK\uYp0྾A1&gF"7f˷w).d&$I+BvVCRZZ-:F&g w\T0̀2'm nJ0awFϱٶu1S%K azT0.Q[R̅D0:ϤS>9Om=)HTJBIt]}q]&WzZE*^? ddOD (!a=Ny퍓y1=^N\>2JM/ /7==Ho悘NosZ,IGĀZ(a"Xx0T YTħS@4 A9eS|A+Xp?G b&Y5M3 o#S;u£8?c NUp|ΜUx~]L@4Dŀ X33SJO__T^Å *ҥ'?/ݜZP˽w-R~#w}sD:&k8s HEEHsF\F j ^t&ģbU%R7K ?ڠ{:`ioqe> ɪ.7ķO{11"3B6oMê Q T|T#߽rL tUS+r{:u6M0`d ,b^{n#׮nF0 u=iU6,W_v̤^zt]xdK' a70G?|q=i X̘ѝ9 dO'0gRk!e=;w]b6E<ȢELGU/kFqHofɲ:CcyDDPUA~X56YBT Vn|^L_p0nC[E@&FjƉXk[OUխZaBlݶe]԰_ mx/Y/r50(}2NFh*zrMHcg GZ[FmXo7W7T< Dm6JXXT^f%bgt=P8=SY/T/BbOM”OiVFnB gkιN̍dz@ C\z#$V0Y3Plʀh췾}/7}mm<3/݆.:tJ}' 0͛9}vB#R##WfiMȍ/Hdq;3PT΀#!hi/4e!wgj H#:g2"3E/_""[Ţfr+#WhrCzrh !S|u, 3#O훽etP'MA]r*]5.Y & #;ϫ(#"}yX$" %`&L25{g7?xm;f@g畩g7 sE$ r;= EhB<7|#j%y AroIS4Ƴh{Fֻُ{ (jA5u#V`D!2wv.3t[]nK@_K0Q):&dĉ7RRƋ932- r%̫f*+o"0H hDEǜBd &{P3&!OB,+# T T 'xX !55̘#%ATD0PQ5JYt0=_!,1_.ԈE r@.|&X\L'R=6asahdw@;j4(#J\sRR I4LOaL0G+$x*+N _$^T|Bf/C.vpHP 'tڟ[pE`?p61 Fy:e@f o¯+C/"XMXV+, #->BF.0>'>̥۷'|PH87PqICO8AN{fL pWБZ&bS` ?9)`r&|C< X̆[*W1P. eYXN9B4x`XNNvE4ֈvjt = c{p= G۶ӜÕy+P` C%aQyE \!^ð:`N=Z`shiSRH[ FC#Twaqgot-11.8.5/package.json000066400000000000000000000053511424347352000146750ustar00rootroot00000000000000{ "name": "got", "version": "11.8.5", "description": "Human-friendly and powerful HTTP request library for Node.js", "license": "MIT", "repository": "sindresorhus/got", "funding": "https://github.com/sindresorhus/got?sponsor=1", "main": "dist/source", "engines": { "node": ">=10.19.0" }, "scripts": { "test": "xo && npm run build && nyc --reporter=html --reporter=text ava", "release": "np", "build": "del-cli dist && tsc", "prepare": "npm run build" }, "files": [ "dist/source" ], "keywords": [ "http", "https", "http2", "get", "got", "url", "uri", "request", "simple", "curl", "wget", "fetch", "net", "network", "gzip", "brotli", "requests", "human-friendly", "axios", "superagent", "node-fetch", "ky" ], "dependencies": { "@sindresorhus/is": "^4.0.0", "@szmarczak/http-timer": "^4.0.5", "@types/cacheable-request": "^6.0.1", "@types/responselike": "^1.0.0", "cacheable-lookup": "^5.0.3", "cacheable-request": "^7.0.2", "decompress-response": "^6.0.0", "http2-wrapper": "^1.0.0-beta.5.2", "lowercase-keys": "^2.0.0", "p-cancelable": "^2.0.0", "responselike": "^2.0.0" }, "devDependencies": { "@ava/typescript": "^1.1.1", "@sindresorhus/tsconfig": "^0.7.0", "@sinonjs/fake-timers": "^6.0.1", "@types/benchmark": "^1.0.33", "@types/express": "^4.17.7", "@types/node": "^14.14.0", "@types/node-fetch": "^2.5.7", "@types/pem": "^1.9.5", "@types/pify": "^3.0.2", "@types/request": "^2.48.5", "@types/sinon": "^9.0.5", "@types/tough-cookie": "^4.0.0", "ava": "^3.11.1", "axios": "^0.20.0", "benchmark": "^2.1.4", "coveralls": "^3.1.0", "create-test-server": "^3.0.1", "del-cli": "^3.0.1", "delay": "^4.4.0", "express": "^4.17.1", "form-data": "^3.0.0", "get-stream": "^6.0.0", "nock": "^13.0.4", "node-fetch": "^2.6.0", "np": "^6.4.0", "nyc": "^15.1.0", "p-event": "^4.2.0", "pem": "^1.14.4", "pify": "^5.0.0", "sinon": "^9.0.3", "slow-stream": "0.0.4", "tempy": "^1.0.0", "to-readable-stream": "^2.1.0", "tough-cookie": "^4.0.0", "typescript": "4.0.3", "xo": "^0.34.1" }, "types": "dist/source", "sideEffects": false, "ava": { "files": [ "test/*" ], "timeout": "1m", "typescript": { "rewritePaths": { "test/": "dist/test/" } } }, "nyc": { "extension": [ ".ts" ], "exclude": [ "**/test/**" ] }, "xo": { "ignores": [ "documentation/examples/*" ], "rules": { "@typescript-eslint/no-empty-function": "off", "node/prefer-global/url": "off", "node/prefer-global/url-search-params": "off", "import/no-anonymous-default-export": "off", "@typescript-eslint/no-implicit-any-catch": "off" } }, "runkitExampleFilename": "./documentation/examples/runkit-example.js" } got-11.8.5/readme.md000066400000000000000000002505311424347352000141700ustar00rootroot00000000000000


Got


Huge thanks to for sponsoring Sindre Sorhus!

(they love Got too!)



> Human-friendly and powerful HTTP request library for Node.js [![Build Status: Linux](https://travis-ci.com/sindresorhus/got.svg?branch=master)](https://travis-ci.com/github/sindresorhus/got) [![Coverage Status](https://coveralls.io/repos/github/sindresorhus/got/badge.svg?branch=master)](https://coveralls.io/github/sindresorhus/got?branch=master) [![Downloads](https://img.shields.io/npm/dm/got.svg)](https://npmjs.com/got) [![Install size](https://packagephobia.now.sh/badge?p=got)](https://packagephobia.now.sh/result?p=got) [Moving from Request?](documentation/migration-guides.md) [*(Note that Request is unmaintained)*](https://github.com/request/request/issues/3142) [See how Got compares to other HTTP libraries](#comparison) For browser usage, we recommend [Ky](https://github.com/sindresorhus/ky) by the same people. ## Highlights - [Promise API](#api) - [Stream API](#streams) - [Pagination API](#pagination) - [HTTP2 support](#http2) - [Request cancelation](#aborting-the-request) - [RFC compliant caching](#cache-adapters) - [Follows redirects](#followredirect) - [Retries on failure](#retry) - [Progress events](#onuploadprogress-progress) - [Handles gzip/deflate/brotli](#decompress) - [Timeout handling](#timeout) - [Errors with metadata](#errors) - [JSON mode](#json-mode) - [WHATWG URL support](#url) - [HTTPS API](#advanced-https-api) - [Hooks](#hooks) - [Instances with custom defaults](#instances) - [Types](#types) - [Composable](documentation/advanced-creation.md#merging-instances) - [Plugins](documentation/lets-make-a-plugin.md) - [Used by 4K+ packages and 1.8M+ repos](https://github.com/sindresorhus/got/network/dependents) - [Actively maintained](https://github.com/sindresorhus/got/graphs/contributors) - [Trusted by many companies](#widely-used) ## Install ``` $ npm install got ``` ## Usage ###### Promise ```js const got = require('got'); (async () => { try { const response = await got('https://sindresorhus.com'); console.log(response.body); //=> ' ...' } catch (error) { console.log(error.response.body); //=> 'Internal server error ...' } })(); ``` ###### JSON ```js const got = require('got'); (async () => { const {body} = await got.post('https://httpbin.org/anything', { json: { hello: 'world' }, responseType: 'json' }); console.log(body.data); //=> {hello: 'world'} })(); ``` See [JSON mode](#json-mode) for more details. ###### Streams ```js const stream = require('stream'); const {promisify} = require('util'); const fs = require('fs'); const got = require('got'); const pipeline = promisify(stream.pipeline); (async () => { await pipeline( got.stream('https://sindresorhus.com'), fs.createWriteStream('index.html') ); // For POST, PUT, PATCH, and DELETE methods, `got.stream` returns a `stream.Writable`. await pipeline( fs.createReadStream('index.html'), got.stream.post('https://sindresorhus.com') ); })(); ``` **Tip:** `from.pipe(to)` doesn't forward errors. Instead, use [`stream.pipeline(from, ..., to, callback)`](https://nodejs.org/api/stream.html#stream_stream_pipeline_streams_callback). **Note:** While `got.post('https://example.com')` resolves, `got.stream.post('https://example.com')` will hang indefinitely until a body is provided. If there's no body on purpose, remember to `.end()` the stream or set the [`body`](#body) option to an empty string. ### API It's a `GET` request by default, but can be changed by using different methods or via [`options.method`](#method). **By default, Got will retry on failure. To disable this option, set [`options.retry`](#retry) to `0`.** #### got(url?, options?) Returns a Promise giving a [Response object](#response) or a [Got Stream](#streams-1) if `options.isStream` is set to true. ##### url Type: `string | object` The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url). Properties from `options` will override properties in the parsed `url`. If no protocol is specified, it will throw a `TypeError`. **Note:** The query string is **not** parsed as search params. Example: ```js got('https://example.com/?query=a b'); //=> https://example.com/?query=a%20b got('https://example.com/', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b // The query string is overridden by `searchParams` got('https://example.com/?query=a b', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b ``` ##### options Type: `object` Any of the [`https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback) options. **Note:** Legacy URL support is disabled. `options.path` is supported only for backwards compatibility. Use `options.pathname` and `options.searchParams` instead. `options.auth` has been replaced with `options.username` & `options.password`. ###### method Type: `string`\ Default: `GET` The HTTP method used to make the request. ###### prefixUrl Type: `string | URL` When specified, `prefixUrl` will be prepended to `url`. The prefix can be any valid URL, either relative or absolute.\ A trailing slash `/` is optional - one will be added automatically. **Note:** `prefixUrl` will be ignored if the `url` argument is a URL instance. **Note:** Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion. For example, when the prefix URL is `https://example.com/foo` and the input is `/bar`, there's ambiguity whether the resulting URL would become `https://example.com/foo/bar` or `https://example.com/bar`. The latter is used by browsers. **Tip:** Useful when used with [`got.extend()`](#custom-endpoints) to create niche-specific Got instances. **Tip:** You can change `prefixUrl` using hooks as long as the URL still includes the `prefixUrl`. If the URL doesn't include it anymore, it will throw. ```js const got = require('got'); (async () => { await got('unicorn', {prefixUrl: 'https://cats.com'}); //=> 'https://cats.com/unicorn' const instance = got.extend({ prefixUrl: 'https://google.com' }); await instance('unicorn', { hooks: { beforeRequest: [ options => { options.prefixUrl = 'https://cats.com'; } ] } }); //=> 'https://cats.com/unicorn' })(); ``` ###### headers Type: `object`\ Default: `{}` Request headers. Existing headers will be overwritten. Headers set to `undefined` will be omitted. ###### isStream Type: `boolean`\ Default: `false` Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.stream(url, options?)`. ###### body Type: `string | Buffer | stream.Readable` or [`form-data` instance](https://github.com/form-data/form-data) **Note #1:** The `body` option cannot be used with the `json` or `form` option. **Note #2:** If you provide this option, `got.stream()` will be read-only. **Note #3:** If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError` unless the method is `GET` and the `allowGetBody` option is set to `true`. **Note #4:** This option is not enumerable and will not be merged with the instance defaults. The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / `fs.createReadStream` instance / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`. ###### json Type: `object | Array | number | string | boolean | null` *(JSON-serializable values)* **Note #1:** If you provide this option, `got.stream()` will be read-only.\ **Note #2:** This option is not enumerable and will not be merged with the instance defaults. JSON body. If the `Content-Type` header is not set, it will be set to `application/json`. ###### context Type: `object` User data. In contrast to other options, `context` is not enumerable. **Note:** The object is never merged, it's just passed through. Got will not modify the object in any way. It's very useful for storing auth tokens: ```js const got = require('got'); const instance = got.extend({ hooks: { beforeRequest: [ options => { if (!options.context || !options.context.token) { throw new Error('Token required'); } options.headers.token = options.context.token; } ] } }); (async () => { const context = { token: 'secret' }; const response = await instance('https://httpbin.org/headers', {context}); // Let's see the headers console.log(response.body); })(); ``` ###### responseType Type: `string`\ Default: `'text'` **Note:** When using streams, this option is ignored. The parsing method. Can be `'text'`, `'json'` or `'buffer'`. The promise also has `.text()`, `.json()` and `.buffer()` methods which return another Got promise for the parsed body.\ It's like setting the options to `{responseType: 'json', resolveBodyOnly: true}` but without affecting the main Got promise. Example: ```js (async () => { const responsePromise = got(url); const bufferPromise = responsePromise.buffer(); const jsonPromise = responsePromise.json(); const [response, buffer, json] = await Promise.all([responsePromise, bufferPromise, jsonPromise]); // `response` is an instance of Got Response // `buffer` is an instance of Buffer // `json` is an object })(); ``` ```js // This const body = await got(url).json(); // is semantically the same as this const body = await got(url, {responseType: 'json', resolveBodyOnly: true}); ``` **Note:** `buffer` will return the raw body buffer. Modifying it will also alter the result of `promise.text()` and `promise.json()`. Before overwritting the buffer, please copy it first via `Buffer.from(buffer)`. See https://github.com/nodejs/node/issues/27080 ###### parseJson Type: `(text: string) => unknown`\ Default: `(text: string) => JSON.parse(text)` A function used to parse JSON responses.
Example Using [`bourne`](https://github.com/hapijs/bourne) to prevent prototype pollution: ```js const got = require('got'); const Bourne = require('@hapi/bourne'); (async () => { const parsed = await got('https://example.com', { parseJson: text => Bourne.parse(text) }).json(); console.log(parsed); })(); ```
###### stringifyJson Type: `(object: unknown) => string`\ Default: `(object: unknown) => JSON.stringify(object)` A function used to stringify the body of JSON requests.
Examples Ignore properties starting with `_`: ```js const got = require('got'); (async () => { await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (key.startsWith('_')) { return; } return value; }), json: { some: 'payload', _ignoreMe: 1234 } }); })(); ``` All numbers as strings: ```js const got = require('got'); (async () => { await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (typeof value === 'number') { return value.toString(); } return value; }), json: { some: 'payload', number: 1 } }); })(); ```
###### resolveBodyOnly Type: `boolean`\ Default: `false` When set to `true` the promise will return the [Response body](#body-1) instead of the [Response](#response) object. ###### cookieJar Type: `object` | [`tough.CookieJar` instance](https://github.com/salesforce/tough-cookie#cookiejar) **Note:** If you provide this option, `options.headers.cookie` will be overridden. Cookie support. You don't have to care about parsing or how to store them. [Example](#cookies). ###### cookieJar.setCookie Type: `Function` The function takes two arguments: `rawCookie` (`string`) and `url` (`string`). ###### cookieJar.getCookieString Type: `Function` The function takes one argument: `url` (`string`). ###### ignoreInvalidCookies Type: `boolean`\ Default: `false` Ignore invalid cookies instead of throwing an error. Only useful when the `cookieJar` option has been set. Not recommended. ###### encoding Type: `string`\ Default: `'utf8'` [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data. To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set [`responseType`](#responseType) to `buffer` instead. Don't set this option to `null`. **Note:** This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`. ###### form Type: `object` **Note #1:** If you provide this option, `got.stream()` will be read-only.\ **Note #2:** This option is not enumerable and will not be merged with the instance defaults. The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj). If the `Content-Type` header is not present, it will be set to `application/x-www-form-urlencoded`. ###### searchParams Type: `string | object | URLSearchParams` Query string that will be added to the request URL. This will override the query string in `url`. If you need to pass in an array, you can do it using a `URLSearchParams` instance: ```js const got = require('got'); const searchParams = new URLSearchParams([['key', 'a'], ['key', 'b']]); got('https://example.com', {searchParams}); console.log(searchParams.toString()); //=> 'key=a&key=b' ``` There are some exceptions in regards to `URLSearchParams` behavior: **Note #1:** `null` values are not stringified, an empty string is used instead. **Note #2:** `undefined` values are not stringified, the entry is skipped instead. ###### timeout Type: `number | object` Milliseconds to wait for the server to end the response before aborting the request with [`got.TimeoutError`](#gottimeouterror) error (a.k.a. `request` property). By default, there's no timeout. This also accepts an `object` with the following fields to constrain the duration of each phase of the request lifecycle: - `lookup` starts when a socket is assigned and ends when the hostname has been resolved. Does not apply when using a Unix domain socket. - `connect` starts when `lookup` completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected. - `secureConnect` starts when `connect` completes and ends when the handshaking process completes (HTTPS only). - `socket` starts when the socket is connected. See [request.setTimeout](https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback). - `response` starts when the request has been written to the socket and ends when the response headers are received. - `send` starts when the socket is connected and ends with the request has been written to the socket. - `request` starts when the request is initiated and ends when the response's end event fires. ###### retry Type: `number | object`\ Default: - limit: `2` - calculateDelay: `({attemptCount, retryOptions, error, computedValue}) => computedValue | Promise` - methods: `GET` `PUT` `HEAD` `DELETE` `OPTIONS` `TRACE` - statusCodes: [`408`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408) [`413`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413) [`429`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) [`500`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) [`502`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502) [`503`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503) [`504`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504) [`521`](https://support.cloudflare.com/hc/en-us/articles/115003011431#521error) [`522`](https://support.cloudflare.com/hc/en-us/articles/115003011431#522error) [`524`](https://support.cloudflare.com/hc/en-us/articles/115003011431#524error) - maxRetryAfter: `undefined` - errorCodes: `ETIMEDOUT` `ECONNRESET` `EADDRINUSE` `ECONNREFUSED` `EPIPE` `ENOTFOUND` `ENETUNREACH` `EAI_AGAIN` An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes. If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`.\ If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request. Delays between retries counts with function `1000 * Math.pow(2, retry - 1) + Math.random() * 100`, where `retry` is attempt number (starts from 1). The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value. The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry). **Note:** The `calculateDelay` function is responsible for the entire cache mechanism, including the `limit` property. To support it, you need to check whether `computedValue` is different than `0`. By default, it retries *only* on the specified methods, status codes, and on these network errors: - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached. - `ECONNRESET`: Connection was forcibly closed by a peer. - `EADDRINUSE`: Could not bind to any free port. - `ECONNREFUSED`: Connection was refused by the server. - `EPIPE`: The remote side of the stream being written has been closed. - `ENOTFOUND`: Couldn't resolve the hostname to an IP address. - `ENETUNREACH`: No internet connection. - `EAI_AGAIN`: DNS lookup timed out. You can retry Got streams too. The implementation looks like this: ```js const got = require('got'); const fs = require('fs'); let writeStream; const fn = (retryCount = 0) => { const stream = got.stream('https://example.com'); stream.retryCount = retryCount; if (writeStream) { writeStream.destroy(); } writeStream = fs.createWriteStream('example.com'); stream.pipe(writeStream); // If you don't attach the listener, it will NOT make a retry. // It automatically checks the listener count so it knows whether to retry or not :) stream.once('retry', fn); }; fn(); ``` ###### followRedirect Type: `boolean`\ Default: `true` Defines if redirect responses should be followed automatically. Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`. This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). ###### methodRewriting Type: `boolean`\ Default: `true` By default, redirects will use [method rewriting](https://tools.ietf.org/html/rfc7231#section-6.4). For example, when sending a POST request and receiving a `302`, it will resend the body to the new location using the same HTTP method (`POST` in this case). ###### allowGetBody Type: `boolean`\ Default: `false` **Note:** The [RFC 7321](https://tools.ietf.org/html/rfc7231#section-4.3.1) doesn't specify any particular behavior for the GET method having a payload, therefore **it's considered an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern)**. Set this to `true` to allow sending body for the `GET` method. However, the [HTTP/2 specification](https://tools.ietf.org/html/rfc7540#section-8.1.3) says that `An HTTP GET request includes request header fields and no payload body`, therefore when using the HTTP/2 protocol this option will have no effect. This option is only meant to interact with non-compliant servers when you have no other choice. ###### maxRedirects Type: `number`\ Default: `10` If exceeded, the request will be aborted and a `MaxRedirectsError` will be thrown. ###### decompress Type: `boolean`\ Default: `true` Decompress the response automatically. This will set the `accept-encoding` header to `gzip, deflate, br` on Node.js 11.7.0+ or `gzip, deflate` for older Node.js versions, unless you set it yourself. Brotli (`br`) support requires Node.js 11.7.0 or later. If this is disabled, a compressed response is returned as a `Buffer`. This may be useful if you want to handle decompression yourself or stream the raw compressed data. ###### cache Type: `object | false`\ Default: `false` [Cache adapter instance](#cache-adapters) for storing cached response data. ###### cacheOptions Type: `object | undefined`\ Default: `{}` [Cache options](https://github.com/kornelski/http-cache-semantics#constructor-options) used for the specified request. ###### dnsCache Type: `CacheableLookup | false`\ Default: `false` An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups. Useful when making lots of requests to different *public* hostnames. **Note:** This should stay disabled when making requests to internal hostnames such as `localhost`, `database.local` etc.\ `CacheableLookup` uses `dns.resolver4(..)` and `dns.resolver6(...)` under the hood and fall backs to `dns.lookup(...)` when the first two fail, which may lead to additional delay. ###### dnsLookupIpVersion Type: `'auto' | 'ipv4' | 'ipv6'`\ Default: `'auto'` Indicates which DNS record family to use.\ Values: - `auto`: IPv4 (if present) or IPv6 - `ipv4`: Only IPv4 - `ipv6`: Only IPv6 Note: If you are using the undocumented option `family`, `dnsLookupIpVersion` will override it. ```js // `api6.ipify.org` will be resolved as IPv4 and the request will be over IPv4 (the website will respond with your public IPv4) await got('https://api6.ipify.org', { dnsLookupIpVersion: 'ipv4' }); // `api6.ipify.org` will be resolved as IPv6 and the request will be over IPv6 (the website will respond with your public IPv6) await got('https://api6.ipify.org', { dnsLookupIpVersion: 'ipv6' }); ``` ###### lookup Type: `Function`\ Default: [`dns.lookup`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback) Custom DNS resolution logic. The function signature is the same as [`dns.lookup`](https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback). ###### request Type: `Function`\ Default: `http.request | https.request` *(Depending on the protocol)* Custom request function. The main purpose of this is to [support HTTP2 using a wrapper](https://github.com/szmarczak/http2-wrapper). ###### http2 Type: `boolean`\ Default: `false` If set to `true`, Got will additionally accept HTTP2 requests.\ It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol. **Note:** Overriding `options.request` will disable HTTP2 support. **Note:** This option will default to `true` in the next upcoming major release. ```js const got = require('got'); (async () => { const {headers} = await got('https://nghttp2.org/httpbin/anything', {http2: true}); console.log(headers.via); //=> '2 nghttpx' })(); ``` ###### throwHttpErrors Type: `boolean`\ Default: `true` Determines if a [`got.HTTPError`](#gothttperror) is thrown for unsuccessful responses. If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses. ###### agent Type: `object` An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance. This is necessary because a request to one protocol might redirect to another. In such a scenario, Got will switch over to the right protocol agent for you. If a key is not present, it will default to a global agent. ```js const got = require('got'); const HttpAgent = require('agentkeepalive'); const {HttpsAgent} = HttpAgent; got('https://sindresorhus.com', { agent: { http: new HttpAgent(), https: new HttpsAgent() } }); ``` ###### hooks Type: `object` Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially. ###### hooks.init Type: `Function[]`\ Default: `[]` Called with plain [request options](#options), right before their normalization. This is especially useful in conjunction with [`got.extend()`](#instances) when the input needs custom handling. See the [Request migration guide](documentation/migration-guides.md#breaking-changes) for an example. **Note #1:** This hook must be synchronous!\ **Note #2:** Errors in this hook will be converted into an instances of [`RequestError`](#gotrequesterror).\ **Note #3:** The options object may not have a `url` property. To modify it, use a `beforeRequest` hook instead. ###### hooks.beforeRequest Type: `Function[]`\ Default: `[]` Called with [normalized](source/core/index.ts) [request options](#options). Got will make no further changes to the request before it is sent. This is especially useful in conjunction with [`got.extend()`](#instances) when you want to create an API client that, for example, uses HMAC-signing. **Note:** Changing `options.json` or `options.form` has no effect on the request, you should change `options.body` instead. If needed, update the `options.headers` accordingly. Example: ```js const got = require('got'); got.post({ json: {payload: 'old'}, hooks: { beforeRequest: [ options => { options.body = JSON.stringify({payload: 'new'}); options.headers['content-length'] = options.body.length.toString(); } ] } }); ``` **Tip:** You can override the `request` function by returning a [`ClientRequest`-like](https://nodejs.org/api/http.html#http_class_http_clientrequest) instance or a [`IncomingMessage`-like](https://nodejs.org/api/http.html#http_class_http_incomingmessage) instance. This is very useful when creating a custom cache mechanism. ###### hooks.beforeRedirect Type: `Function[]`\ Default: `[]` Called with [normalized](source/core/index.ts) [request options](#options) and the redirect [response](#response). Got will make no further changes to the request. This is especially useful when you want to avoid dead sites. Example: ```js const got = require('got'); got('https://example.com', { hooks: { beforeRedirect: [ (options, response) => { if (options.hostname === 'deadSite') { options.hostname = 'fallbackSite'; } } ] } }); ``` ###### hooks.beforeRetry Type: `Function[]`\ Default: `[]` **Note:** When using streams, this hook is ignored. Called with [normalized](source/normalize-arguments.ts) [request options](#options), the error and the retry count. Got will make no further changes to the request. This is especially useful when some extra work is required before the next try. Example: ```js const got = require('got'); got.post('https://example.com', { hooks: { beforeRetry: [ (options, error, retryCount) => { if (error.response.statusCode === 413) { // Payload too large options.body = getNewBody(); } } ] } }); ``` **Note:** When retrying in a `afterResponse` hook, all remaining `beforeRetry` hooks will be called without the `error` and `retryCount` arguments. ###### hooks.afterResponse Type: `Function[]`\ Default: `[]` **Note:** When using streams, this hook is ignored. Called with [response object](#response) and a retry function. Calling the retry function will trigger `beforeRetry` hooks. Each function should return the response. This is especially useful when you want to refresh an access token. Example: ```js const got = require('got'); const instance = got.extend({ hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { // Unauthorized const updatedOptions = { headers: { token: getNewToken() // Refresh the access token } }; // Save for further requests instance.defaults.options = got.mergeOptions(instance.defaults.options, updatedOptions); // Make a new retry return retryWithMergedOptions(updatedOptions); } // No changes otherwise return response; } ], beforeRetry: [ (options, error, retryCount) => { // This will be called on `retryWithMergedOptions(...)` } ] }, mutableDefaults: true }); ``` ###### hooks.beforeError Type: `Function[]`\ Default: `[]` Called with an `Error` instance. The error is passed to the hook right before it's thrown. This is especially useful when you want to have more detailed errors. **Note:** Errors thrown while normalizing input options are thrown directly and not part of this hook. ```js const got = require('got'); got('https://api.github.com/some-endpoint', { hooks: { beforeError: [ error => { const {response} = error; if (response && response.body) { error.name = 'GitHubError'; error.message = `${response.body.message} (${response.statusCode})`; } return error; } ] } }); ``` ##### pagination Type: `object` **Note:** We're [looking for feedback](https://github.com/sindresorhus/got/issues/1052), any ideas on how to improve the API are welcome. ###### pagination.transform Type: `Function`\ Default: `response => JSON.parse(response.body)` A function that transform [`Response`](#response) into an array of items. This is where you should do the parsing. ###### pagination.paginate Type: `Function`\ Default: [`Link` header logic](source/index.ts) The function takes three arguments: - `response` - The current response object. - `allItems` - An array of the emitted items. - `currentItems` - Items from the current response. It should return an object representing Got options pointing to the next page. The options are merged automatically with the previous request, therefore the options returned `pagination.paginate(...)` must reflect changes only. If there are no more pages, `false` should be returned. For example, if you want to stop when the response contains less items than expected, you can use something like this: ```js const got = require('got'); (async () => { const limit = 10; const items = got.paginate('https://example.com/items', { searchParams: { limit, offset: 0 }, pagination: { paginate: (response, allItems, currentItems) => { const previousSearchParams = response.request.options.searchParams; const previousOffset = previousSearchParams.get('offset'); if (currentItems.length < limit) { return false; } return { searchParams: { ...previousSearchParams, offset: Number(previousOffset) + limit, } }; } } }); console.log('Items from all pages:', items); })(); ``` ###### pagination.filter Type: `Function`\ Default: `(item, allItems, currentItems) => true` Checks whether the item should be emitted or not. ###### pagination.shouldContinue Type: `Function`\ Default: `(item, allItems, currentItems) => true` Checks whether the pagination should continue. For example, if you need to stop **before** emitting an entry with some flag, you should use `(item, allItems, currentItems) => !item.flag`. If you want to stop **after** emitting the entry, you should use `(item, allItems, currentItems) => allItems.some(entry => entry.flag)` instead. ###### pagination.countLimit Type: `number`\ Default: `Infinity` The maximum amount of items that should be emitted. ###### pagination.backoff Type: `number`\ Default: `0` Milliseconds to wait before the next request is triggered. ###### pagination.requestLimit Type: `number`\ Default: `10000` The maximum amount of request that should be triggered. [Retries on failure](#retry) are not counted towards this limit. For example, it can be helpful during development to avoid an infinite number of requests. ###### pagination.stackAllItems Type: `boolean`\ Default: `true` Defines how the parameter `allItems` in [pagination.paginate](#pagination.paginate), [pagination.filter](#pagination.filter) and [pagination.shouldContinue](#pagination.shouldContinue) is managed. When set to `false`, the parameter `allItems` is always an empty array. This option can be helpful to save on memory usage when working with a large dataset. ##### localAddress Type: `string` The IP address used to send the request from. ### Advanced HTTPS API Note: If the request is not HTTPS, these options will be ignored. ##### https.certificateAuthority Type: `string | Buffer | Array` Override the default Certificate Authorities ([from Mozilla](https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport)) ```js // Single Certificate Authority got('https://example.com', { https: { certificateAuthority: fs.readFileSync('./my_ca.pem') } }); ``` ##### https.key Type: `string | Buffer | Array | object[]` Private keys in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format.\ [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) allows the option of private keys being encrypted. Encrypted keys will be decrypted with `options.https.passphrase`.\ Multiple keys with different passphrases can be provided as an array of `{pem: , passphrase: }` ##### https.certificate Type: `string | Buffer | (string | Buffer)[]` [Certificate chains](https://en.wikipedia.org/wiki/X.509#Certificate_chains_and_cross-certification) in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format.\ One cert chain should be provided per private key (`options.https.key`).\ When providing multiple cert chains, they do not have to be in the same order as their private keys in `options.https.key`.\ If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail. ##### https.passphrase Type: `string` The passphrase to decrypt the `options.https.key` (if different keys have different passphrases refer to `options.https.key` documentation). ##### https.pfx Type: `string | Buffer | Array` [PFX or PKCS12](https://en.wikipedia.org/wiki/PKCS_12) encoded private key and certificate chain. Using `options.https.pfx` is an alternative to providing `options.https.key` and `options.https.certificate` individually. A PFX is usually encrypted, and if it is, `options.https.passphrase` will be used to decrypt it. Multiple PFX's can be be provided as an array of unencrypted buffers or an array of objects like: ```ts { buffer: string | Buffer, passphrase?: string } ``` This object form can only occur in an array. If the provided buffers are encrypted, `object.passphrase` can be used to decrypt them. If `object.passphrase` is not provided, `options.https.passphrase` will be used for decryption. ##### Examples for `https.key`, `https.certificate`, `https.passphrase`, and `https.pfx` ```js // Single key with certificate got('https://example.com', { https: { key: fs.readFileSync('./client_key.pem'), certificate: fs.readFileSync('./client_cert.pem') } }); // Multiple keys with certificates (out of order) got('https://example.com', { https: { key: [ fs.readFileSync('./client_key1.pem'), fs.readFileSync('./client_key2.pem') ], certificate: [ fs.readFileSync('./client_cert2.pem'), fs.readFileSync('./client_cert1.pem') ] } }); // Single key with passphrase got('https://example.com', { https: { key: fs.readFileSync('./client_key.pem'), certificate: fs.readFileSync('./client_cert.pem'), passphrase: 'client_key_passphrase' } }); // Multiple keys with different passphrases got('https://example.com', { https: { key: [ {pem: fs.readFileSync('./client_key1.pem'), passphrase: 'passphrase1'}, {pem: fs.readFileSync('./client_key2.pem'), passphrase: 'passphrase2'}, ], certificate: [ fs.readFileSync('./client_cert1.pem'), fs.readFileSync('./client_cert2.pem') ] } }); // Single encrypted PFX with passphrase got('https://example.com', { https: { pfx: fs.readFileSync('./fake.pfx'), passphrase: 'passphrase' } }); // Multiple encrypted PFX's with different passphrases got('https://example.com', { https: { pfx: [ { buffer: fs.readFileSync('./key1.pfx'), passphrase: 'passphrase1' }, { buffer: fs.readFileSync('./key2.pfx'), passphrase: 'passphrase2' } ] } }); // Multiple encrypted PFX's with single passphrase got('https://example.com', { https: { passphrase: 'passphrase', pfx: [ { buffer: fs.readFileSync('./key1.pfx') }, { buffer: fs.readFileSync('./key2.pfx') } ] } }); ``` ##### https.rejectUnauthorized Type: `boolean`\ Default: `true` If set to `false`, all invalid SSL certificates will be ignored and no error will be thrown.\ If set to `true`, it will throw an error whenever an invalid SSL certificate is detected. We strongly recommend to have this set to `true` for security reasons. ```js const got = require('got'); (async () => { // Correct: await got('https://example.com', { https: { rejectUnauthorized: true } }); // You can disable it when developing an HTTPS app: await got('https://localhost', { https: { rejectUnauthorized: false } }); // Never do this: await got('https://example.com', { https: { rejectUnauthorized: false } }); ``` ##### https.checkServerIdentity Type: `Function`\ Signature: `(hostname: string, certificate: DetailedPeerCertificate) => Error | undefined`\ Default: `tls.checkServerIdentity` (from the `tls` module) This function enable a custom check of the certificate.\ Note: In order to have the function called the certificate must not be `expired`, `self-signed` or with an `untrusted-root`.\ The function parameters are: - `hostname`: The server hostname (used when connecting) - `certificate`: The server certificate The function must return `undefined` if the check succeeded or an `Error` if it failed. ```js await got('https://example.com', { https: { checkServerIdentity: (hostname, certificate) => { if (hostname === 'example.com') { return; // Certificate OK } return new Error('Invalid Hostname'); // Certificate NOT OK } } }); ``` #### Response The response object will typically be a [Node.js HTTP response stream](https://nodejs.org/api/http.html#http_class_http_incomingmessage), however, if returned from the cache it will be a [response-like object](https://github.com/lukechilds/responselike) which behaves in the same way. ##### request Type: `object` **Note:** This is not a [http.ClientRequest](https://nodejs.org/api/http.html#http_class_http_clientrequest). - `options` - The Got options that were set on this request. ##### body Type: `string | object | Buffer` *(Depending on `options.responseType`)* The result of the request. ##### rawBody Type: `Buffer` The raw result of the request. ##### url Type: `string` The request URL or the final URL after redirects. ##### ip Type: `string` The remote IP address. **Note:** Not available when the response is cached. This is hopefully a temporary limitation, see [lukechilds/cacheable-request#86](https://github.com/lukechilds/cacheable-request/issues/86). ##### requestUrl Type: `string` The original request URL. ##### timings Type: `object` The object contains the following properties: - `start` - Time when the request started. - `socket` - Time when a socket was assigned to the request. - `lookup` - Time when the DNS lookup finished. - `connect` - Time when the socket successfully connected. - `secureConnect` - Time when the socket securely connected. - `upload` - Time when the request finished uploading. - `response` - Time when the request fired `response` event. - `end` - Time when the response fired `end` event. - `error` - Time when the request fired `error` event. - `abort` - Time when the request fired `abort` event. - `phases` - `wait` - `timings.socket - timings.start` - `dns` - `timings.lookup - timings.socket` - `tcp` - `timings.connect - timings.lookup` - `tls` - `timings.secureConnect - timings.connect` - `request` - `timings.upload - (timings.secureConnect || timings.connect)` - `firstByte` - `timings.response - timings.upload` - `download` - `timings.end - timings.response` - `total` - `(timings.end || timings.error || timings.abort) - timings.start` If something has not been measured yet, it will be `undefined`. **Note:** The time is a `number` representing the milliseconds elapsed since the UNIX epoch. ##### isFromCache Type: `boolean` Whether the response was retrieved from the cache. ##### redirectUrls Type: `string[]` The redirect URLs. ##### retryCount Type: `number` The number of times the request was retried. #### Streams **Note:** Progress events, redirect events and request/response events can also be used with promises. **Note:** To access `response.isFromCache` you need to use `got.stream(url, options).isFromCache`. The value will be undefined until the `response` event. #### got.stream(url, options?) Sets `options.isStream` to `true`. Returns a [duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) with additional events: ##### .on('request', request) `request` event to get the request object of the request. **Tip:** You can use `request` event to abort request: ```js got.stream('https://github.com') .on('request', request => setTimeout(() => request.destroy(), 50)); ``` ##### .on('response', response) The `response` event to get the response object of the final request. ##### .on('redirect', response, nextOptions) The `redirect` event to get the response object of a redirect. The second argument is options for the next request to the redirect location. ##### .on('uploadProgress', progress) ##### .uploadProgress ##### .on('downloadProgress', progress) ##### .downloadProgress Progress events for uploading (sending a request) and downloading (receiving a response). The `progress` argument is an object like: ```js { percent: 0.1, transferred: 1024, total: 10240 } ``` If the `content-length` header is missing, `total` will be `undefined`. ```js (async () => { const response = await got('https://sindresorhus.com') .on('downloadProgress', progress => { // Report download progress }) .on('uploadProgress', progress => { // Report upload progress }); console.log(response); })(); ``` ##### .once('retry', retryCount, error) To enable retrying on a Got stream, it is required to have a `retry` handler attached.\ When this event is emitted, you should reset the stream you were writing to and prepare the body again. See the [`retry`](#retry-stream) option for an example implementation. ##### .ip Type: `string` The remote IP address. ##### .aborted Type: `boolean` Indicates whether the request has been aborted or not. ##### .timings The same as `response.timings`. ##### .isFromCache The same as `response.isFromCache`. ##### .socket The same as `response.socket`. ##### .on('error', error) The emitted `error` is an instance of [`RequestError`](#gotrequesterror). #### Pagination #### got.paginate(url, options?) #### got.paginate.each(url, options?) Returns an async iterator: ```js (async () => { const countLimit = 10; const pagination = got.paginate('https://api.github.com/repos/sindresorhus/got/commits', { pagination: {countLimit} }); console.log(`Printing latest ${countLimit} Got commits (newest to oldest):`); for await (const commitData of pagination) { console.log(commitData.commit.message); } })(); ``` See [`options.pagination`](#pagination) for more pagination options. #### got.paginate.all(url, options?) Returns a Promise for an array of all results: ```js (async () => { const countLimit = 10; const results = await got.paginate.all('https://api.github.com/repos/sindresorhus/got/commits', { pagination: {countLimit} }); console.log(`Printing latest ${countLimit} Got commits (newest to oldest):`); console.log(results); })(); ``` See [`options.pagination`](#pagination) for more pagination options. #### got.get(url, options?) #### got.post(url, options?) #### got.put(url, options?) #### got.patch(url, options?) #### got.head(url, options?) #### got.delete(url, options?) Sets [`options.method`](#method) to the method name and makes a request. ### Instances #### got.extend(...options) Configure a new `got` instance with default `options`. The `options` are merged with the parent instance's `defaults.options` using [`got.mergeOptions`](#gotmergeoptionsparentoptions-newoptions). You can access the resolved options with the `.defaults` property on the instance. ```js const client = got.extend({ prefixUrl: 'https://example.com', headers: { 'x-unicorn': 'rainbow' } }); client.get('demo'); /* HTTP Request => * GET /demo HTTP/1.1 * Host: example.com * x-unicorn: rainbow */ ``` ```js (async () => { const client = got.extend({ prefixUrl: 'httpbin.org', headers: { 'x-foo': 'bar' } }); const {headers} = await client.get('headers').json(); //=> headers['x-foo'] === 'bar' const jsonClient = client.extend({ responseType: 'json', resolveBodyOnly: true, headers: { 'x-baz': 'qux' } }); const {headers: headers2} = await jsonClient.get('headers'); //=> headers2['x-foo'] === 'bar' //=> headers2['x-baz'] === 'qux' })(); ``` Additionally, `got.extend()` accepts two properties from the `defaults` object: `mutableDefaults` and `handlers`. Example: ```js // You can now modify `mutableGot.defaults.options`. const mutableGot = got.extend({mutableDefaults: true}); const mergedHandlers = got.extend({ handlers: [ (options, next) => { delete options.headers.referer; return next(options); } ] }); ``` **Note:** Handlers can be asynchronous. The recommended approach is: ```js const handler = (options, next) => { if (options.isStream) { // It's a Stream return next(options); } // It's a Promise return (async () => { try { const response = await next(options); response.yourOwnProperty = true; return response; } catch (error) { // Every error will be replaced by this one. // Before you receive any error here, // it will be passed to the `beforeError` hooks first. // Note: this one won't be passed to `beforeError` hook. It's final. throw new Error('Your very own error.'); } })(); }; const instance = got.extend({handlers: [handler]}); ``` #### got.extend(...options, ...instances, ...) Merges many instances into a single one: - options are merged using [`got.mergeOptions()`](#gotmergeoptionsparentoptions-newoptions) (including hooks), - handlers are stored in an array (you can access them through `instance.defaults.handlers`). ```js const a = {headers: {cat: 'meow'}}; const b = got.extend({ options: { headers: { cow: 'moo' } } }); // The same as `got.extend(a).extend(b)`. // Note `a` is options and `b` is an instance. got.extend(a, b); //=> {headers: {cat: 'meow', cow: 'moo'}} ``` #### got.mergeOptions(parent, ...sources) Extends parent options. Avoid using [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) as it doesn't work recursively: ```js const a = {headers: {cat: 'meow', wolf: ['bark', 'wrrr']}}; const b = {headers: {cow: 'moo', wolf: ['auuu']}}; {...a, ...b} // => {headers: {cow: 'moo', wolf: ['auuu']}} got.mergeOptions(a, b) // => {headers: {cat: 'meow', cow: 'moo', wolf: ['auuu']}} ``` **Note:** Only Got options are merged! Custom user options should be defined via [`options.context`](#context). Options are deeply merged to a new object. The value of each key is determined as follows: - If the new property is not defined, the old value is used. - If the new property is explicitly set to `undefined`: - If the parent property is a plain `object`, the parent value is deeply cloned. - Otherwise, `undefined` is used. - If the parent value is an instance of `URLSearchParams`: - If the new value is a `string`, an `object` or an instance of `URLSearchParams`, a new `URLSearchParams` instance is created. The values are merged using [`urlSearchParams.append(key, value)`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/append). The keys defined in the new value override the keys defined in the parent value. Please note that `null` values point to an empty string and `undefined` values will exclude the entry. - Otherwise, the only available value is `undefined`. - If the new property is a plain `object`: - If the parent property is a plain `object` too, both values are merged recursively into a new `object`. - Otherwise, only the new value is deeply cloned. - If the new property is an `Array`, it overwrites the old one with a deep clone of the new property. - Properties that are not enumerable, such as `context`, `body`, `json`, and `form`, will not be merged. - Otherwise, the new value is assigned to the key. ```js const a = {json: {cat: 'meow'}}; const b = {json: {cow: 'moo'}}; got.mergeOptions(a, b); //=> {json: {cow: 'moo'}} ``` #### got.defaults Type: `object` The Got defaults used in that instance. ##### [options](#options) ##### handlers Type: `Function[]`\ Default: `[]` An array of functions. You execute them directly by calling `got()`. They are some sort of "global hooks" - these functions are called first. The last handler (*it's hidden*) is either [`asPromise`](source/core/as-promise/index.ts) or [`asStream`](source/core/index.ts), depending on the `options.isStream` property. Each handler takes two arguments: ###### [options](#options) ###### next() Returns a `Promise` or a `Stream` depending on [`options.isStream`](#isstream). ```js const settings = { handlers: [ (options, next) => { if (options.isStream) { // It's a Stream, so we can perform stream-specific actions on it return next(options) .on('request', request => { setTimeout(() => { request.abort(); }, 50); }); } // It's a Promise return next(options); } ], options: got.mergeOptions(got.defaults.options, { responseType: 'json' }) }; const jsonGot = got.extend(settings); ``` ##### mutableDefaults Type: `boolean`\ Default: `false` A read-only boolean describing whether the defaults are mutable or not. If set to `true`, you can [update headers over time](#hooksafterresponse), for example, update an access token when it expires. ## Types Got exports some handy TypeScript types and interfaces. See the type definition for all the exported types. ### Got TypeScript will automatically infer types for Got instances, but in case you want to define something like dependencies, you can import the available types directly from Got. ```ts import {GotRequestFunction} from 'got'; interface Dependencies { readonly post: GotRequestFunction } ``` ### Hooks When writing hooks, you can refer to their types to keep your interfaces consistent. ```ts import {BeforeRequestHook} from 'got'; const addAccessToken = (accessToken: string): BeforeRequestHook => options => { options.path = `${options.path}?access_token=${accessToken}`; } ``` ## Errors Each error contains an `options` property which are the options Got used to create a request - just to make debugging easier.\ Additionaly, the errors may have `request` (Got Stream) and `response` (Got Response) properties depending on which phase of the request failed. #### got.RequestError When a request fails. Contains a `code` property with error class code, like `ECONNREFUSED`. If there is no specific code supplied, `code` defaults to `ERR_GOT_REQUEST_ERROR`. All the errors below inherit this one. #### got.CacheError When a cache method fails, for example, if the database goes down or there's a filesystem error. Contains a `code` property with `ERR_CACHE_ACCESS` or a more specific failure code. #### got.ReadError When reading from response stream fails. Contains a `code` property with `ERR_READING_RESPONSE_STREAM` or a more specific failure code. #### got.ParseError When server response code is 2xx, and parsing body fails. Includes a `response` property. Contains a `code` property with `ERR_BODY_PARSE_FAILURE` or a more specific failure code. #### got.UploadError When the request body is a stream and an error occurs while reading from that stream. Contains a `code` property with `ERR_UPLOAD` or a more specific failure code. #### got.HTTPError When the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304. Includes a `response` property. Contains a `code` property with `ERR_NON_2XX_3XX_RESPONSE` or a more specific failure code. #### got.MaxRedirectsError When the server redirects you more than ten times. Includes a `response` property. Contains a `code` property with `ERR_TOO_MANY_REDIRECTS`. #### got.UnsupportedProtocolError When given an unsupported protocol. Contains a `code` property with `ERR_UNSUPPORTED_PROTOCOL`. #### got.TimeoutError When the request is aborted due to a [timeout](#timeout). Includes an `event` and `timings` property. Contains a `code` property with `ETIMEDOUT`. #### got.CancelError When the request is aborted with `.cancel()`. Contains a `code` property with `ERR_CANCELED`. ## Aborting the request The promise returned by Got has a [`.cancel()`](https://github.com/sindresorhus/p-cancelable) method which when called, aborts the request. ```js (async () => { const request = got(url, options); // … // In another part of the code if (something) { request.cancel(); } // … try { await request; } catch (error) { if (request.isCanceled) { // Or `error instanceof got.CancelError` // Handle cancelation } // Handle other errors } })(); ``` When using hooks, simply throw an error to abort the request. ```js const got = require('got'); (async () => { const request = got(url, { hooks: { beforeRequest: [ () => { throw new Error('Oops. Request canceled.'); } ] } }); try { await request; } catch (error) { // … } })(); ``` To abort the Got Stream request, just call `stream.destroy()`. ```js const got = require('got'); const stream = got.stream(url); stream.destroy(); ``` ## Cache Got implements [RFC 7234](https://httpwg.org/specs/rfc7234.html) compliant HTTP caching which works out of the box in-memory and is easily pluggable with a wide range of storage adapters. Fresh cache entries are served directly from the cache, and stale cache entries are revalidated with `If-None-Match`/`If-Modified-Since` headers. You can read more about the underlying cache behavior in the [`cacheable-request` documentation](https://github.com/lukechilds/cacheable-request). For DNS cache, Got uses [`cacheable-lookup`](https://github.com/szmarczak/cacheable-lookup). You can use the JavaScript `Map` type as an in-memory cache: ```js const got = require('got'); const map = new Map(); (async () => { let response = await got('https://sindresorhus.com', {cache: map}); console.log(response.isFromCache); //=> false response = await got('https://sindresorhus.com', {cache: map}); console.log(response.isFromCache); //=> true })(); ``` Got uses [Keyv](https://github.com/lukechilds/keyv) internally to support a wide range of storage adapters. For something more scalable you could use an [official Keyv storage adapter](https://github.com/lukechilds/keyv#official-storage-adapters): ``` $ npm install @keyv/redis ``` ```js const got = require('got'); const KeyvRedis = require('@keyv/redis'); const redis = new KeyvRedis('redis://user:pass@localhost:6379'); got('https://sindresorhus.com', {cache: redis}); ``` Got supports anything that follows the Map API, so it's easy to write your own storage adapter or use a third-party solution. For example, the following are all valid storage adapters: ```js const storageAdapter = new Map(); // Or const storageAdapter = require('./my-storage-adapter'); // Or const QuickLRU = require('quick-lru'); const storageAdapter = new QuickLRU({maxSize: 1000}); got('https://sindresorhus.com', {cache: storageAdapter}); ``` View the [Keyv docs](https://github.com/lukechilds/keyv) for more information on how to use storage adapters. ## Proxies You can use the [`tunnel`](https://github.com/koichik/node-tunnel) package with the `agent` option to work with proxies: ```js const got = require('got'); const tunnel = require('tunnel'); got('https://sindresorhus.com', { agent: { https: tunnel.httpsOverHttp({ proxy: { host: 'localhost' } }) } }); ``` Otherwise, you can use the [`hpagent`](https://github.com/delvedor/hpagent) package, which keeps the internal sockets alive to be reused. ```js const got = require('got'); const {HttpsProxyAgent} = require('hpagent'); got('https://sindresorhus.com', { agent: { https: new HttpsProxyAgent({ keepAlive: true, keepAliveMsecs: 1000, maxSockets: 256, maxFreeSockets: 256, scheduling: 'lifo', proxy: 'https://localhost:8080' }) } }); ``` Alternatively, use [`global-agent`](https://github.com/gajus/global-agent) to configure a global proxy for all HTTP/HTTPS traffic in your program. Read the [`http2-wrapper`](https://github.com/szmarczak/http2-wrapper/#proxy-support) docs to learn about proxying for HTTP/2. ## Cookies You can use the [`tough-cookie`](https://github.com/salesforce/tough-cookie) package: ```js const {promisify} = require('util'); const got = require('got'); const {CookieJar} = require('tough-cookie'); (async () => { const cookieJar = new CookieJar(); const setCookie = promisify(cookieJar.setCookie.bind(cookieJar)); await setCookie('foo=bar', 'https://example.com'); await got('https://example.com', {cookieJar}); })(); ``` ## Form data You can use the [`form-data`](https://github.com/form-data/form-data) package to create POST request with form data: ```js const fs = require('fs'); const got = require('got'); const FormData = require('form-data'); const form = new FormData(); form.append('my_file', fs.createReadStream('/foo/bar.jpg')); got.post('https://example.com', { body: form }); ``` ## OAuth You can use the [`oauth-1.0a`](https://github.com/ddo/oauth-1.0a) package to create a signed OAuth request: ```js const got = require('got'); const crypto = require('crypto'); const OAuth = require('oauth-1.0a'); const oauth = OAuth({ consumer: { key: process.env.CONSUMER_KEY, secret: process.env.CONSUMER_SECRET }, signature_method: 'HMAC-SHA1', hash_function: (baseString, key) => crypto.createHmac('sha1', key).update(baseString).digest('base64') }); const token = { key: process.env.ACCESS_TOKEN, secret: process.env.ACCESS_TOKEN_SECRET }; const url = 'https://api.twitter.com/1.1/statuses/home_timeline.json'; got(url, { headers: oauth.toHeader(oauth.authorize({url, method: 'GET'}, token)), responseType: 'json' }); ``` ## Unix Domain Sockets Requests can also be sent via [unix domain sockets](http://serverfault.com/questions/124517/whats-the-difference-between-unix-socket-and-tcp-ip-socket). Use the following URL scheme: `PROTOCOL://unix:SOCKET:PATH`. - `PROTOCOL` - `http` or `https` *(optional)* - `SOCKET` - Absolute path to a unix domain socket, for example: `/var/run/docker.sock` - `PATH` - Request path, for example: `/v2/keys` ```js const got = require('got'); got('http://unix:/var/run/docker.sock:/containers/json'); // Or without protocol (HTTP by default) got('unix:/var/run/docker.sock:/containers/json'); ``` ## AWS Requests to AWS services need to have their headers signed. This can be accomplished by using the [`got4aws`](https://www.npmjs.com/package/got4aws) package. This is an example for querying an ["API Gateway"](https://docs.aws.amazon.com/apigateway/api-reference/signing-requests/) with a signed request. ```js const got4aws = require('got4aws');; const awsClient = got4aws(); const response = await awsClient('https://.execute-api..amazonaws.com//endpoint/path', { // Request-specific options }); ``` ## Testing You can test your requests by using the [`nock`](https://github.com/node-nock/nock) package to mock an endpoint: ```js const got = require('got'); const nock = require('nock'); nock('https://sindresorhus.com') .get('/') .reply(200, 'Hello world!'); (async () => { const response = await got('https://sindresorhus.com'); console.log(response.body); //=> 'Hello world!' })(); ``` Bear in mind, that by default `nock` mocks only one request. Got will [retry](#retry) on failed requests by default, causing a `No match for request ...` error. The solution is to either disable retrying (set `options.retry` to `0`) or call `.persist()` on the mocked request. ```js const got = require('got'); const nock = require('nock'); const scope = nock('https://sindresorhus.com') .get('/') .reply(500, 'Internal server error') .persist(); (async () => { try { await got('https://sindresorhus.com') } catch (error) { console.log(error.response.body); //=> 'Internal server error' console.log(error.response.retryCount); //=> 2 } scope.persist(false); })(); ``` For real integration testing we recommend using [`ava`](https://github.com/avajs/ava) with [`create-test-server`](https://github.com/lukechilds/create-test-server). We're using a macro so we don't have to `server.listen()` and `server.close()` every test. Take a look at one of our tests: ```js test('retry function gets iteration count', withServer, async (t, server, got) => { let knocks = 0; server.get('/', (request, response) => { if (knocks++ === 1) { response.end('who`s there?'); } }); await got({ retry: { calculateDelay: ({attemptCount}) => { t.true(is.number(attemptCount)); return attemptCount < 2 ? 1 : 0; } } }); }); ``` ## Tips ### JSON mode To pass an object as the body, you need to use the `json` option. It will be stringified using `JSON.stringify`. Example: ```js const got = require('got'); (async () => { const {body} = await got.post('https://httpbin.org/anything', { json: { hello: 'world' }, responseType: 'json' }); console.log(body.data); //=> '{"hello":"world"}' })(); ``` To receive a JSON body you can either set `responseType` option to `json` or use `promise.json()`. Example: ```js const got = require('got'); (async () => { const body = await got.post('https://httpbin.org/anything', { json: { hello: 'world' } }).json(); console.log(body); //=> {…} })(); ``` ### User Agent It's a good idea to set the `'user-agent'` header so the provider can more easily see how their resource is used. By default, it's the URL to this repo. You can omit this header by setting it to `undefined`. ```js const got = require('got'); const pkg = require('./package.json'); got('https://sindresorhus.com', { headers: { 'user-agent': `my-package/${pkg.version} (https://github.com/username/my-package)` } }); got('https://sindresorhus.com', { headers: { 'user-agent': undefined } }); ``` ### 304 Responses Bear in mind; if you send an `if-modified-since` header and receive a `304 Not Modified` response, the body will be empty. It's your responsibility to cache and retrieve the body contents. ### Custom endpoints Use `got.extend()` to make it nicer to work with REST APIs. Especially if you use the `prefixUrl` option. ```js const got = require('got'); const pkg = require('./package.json'); const custom = got.extend({ prefixUrl: 'example.com', responseType: 'json', headers: { 'user-agent': `my-package/${pkg.version} (https://github.com/username/my-package)` } }); // Use `custom` exactly how you use `got` (async () => { const list = await custom('v1/users/list'); })(); ``` ## FAQ ### Why yet another HTTP client? Got was created because the popular [`request`](https://github.com/request/request) package is bloated: [![Install size](https://packagephobia.now.sh/badge?p=request)](https://packagephobia.now.sh/result?p=request)\ Furthermore, Got is fully written in TypeScript and actively maintained. ### Electron support has been removed The Electron `net` module is not consistent with the Node.js `http` module. See [#899](https://github.com/sindresorhus/got/issues/899) for more info. ## Comparison | | `got` | [`request`][r0] | [`node-fetch`][n0] | [`ky`][k0] | [`axios`][a0] | [`superagent`][s0] | |-----------------------|:------------------:|:------------------:|:--------------------:|:------------------------:|:------------------:|:----------------------:| | HTTP/2 support | :sparkle: | :x: | :x: | :x: | :x: | :heavy_check_mark:\*\* | | Browser support | :x: | :x: | :heavy_check_mark:\* | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Promise API | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Stream API | :heavy_check_mark: | :heavy_check_mark: | Node.js only | :x: | :x: | :heavy_check_mark: | | Pagination API | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | | Request cancelation | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | RFC compliant caching | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | | Cookies (out-of-box) | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | | Follows redirects | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Retries on failure | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :x: | :heavy_check_mark: | | Progress events | :heavy_check_mark: | :x: | :x: | :heavy_check_mark:\*\*\* | Browser only | :heavy_check_mark: | | Handles gzip/deflate | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Advanced timeouts | :heavy_check_mark: | :x: | :x: | :x: | :x: | :x: | | Timings | :heavy_check_mark: | :heavy_check_mark: | :x: | :x: | :x: | :x: | | Errors with metadata | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | | JSON mode | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | Custom defaults | :heavy_check_mark: | :heavy_check_mark: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | | Composable | :heavy_check_mark: | :x: | :x: | :x: | :x: | :heavy_check_mark: | | Hooks | :heavy_check_mark: | :x: | :x: | :heavy_check_mark: | :heavy_check_mark: | :x: | | Issues open | [![][gio]][g1] | [![][rio]][r1] | [![][nio]][n1] | [![][kio]][k1] | [![][aio]][a1] | [![][sio]][s1] | | Issues closed | [![][gic]][g2] | [![][ric]][r2] | [![][nic]][n2] | [![][kic]][k2] | [![][aic]][a2] | [![][sic]][s2] | | Downloads | [![][gd]][g3] | [![][rd]][r3] | [![][nd]][n3] | [![][kd]][k3] | [![][ad]][a3] | [![][sd]][s3] | | Coverage | [![][gc]][g4] | [![][rc]][r4] | [![][nc]][n4] | [![][kc]][k4] | [![][ac]][a4] | [![][sc]][s4] | | Build | [![][gb]][g5] | [![][rb]][r5] | [![][nb]][n5] | [![][kb]][k5] | [![][ab]][a5] | [![][sb]][s5] | | Bugs | [![][gbg]][g6] | [![][rbg]][r6] | [![][nbg]][n6] | [![][kbg]][k6] | [![][abg]][a6] | [![][sbg]][s6] | | Dependents | [![][gdp]][g7] | [![][rdp]][r7] | [![][ndp]][n7] | [![][kdp]][k7] | [![][adp]][a7] | [![][sdp]][s7] | | Install size | [![][gis]][g8] | [![][ris]][r8] | [![][nis]][n8] | [![][kis]][k8] | [![][ais]][a8] | [![][sis]][s8] | | GitHub stars | [![][gs]][g9] | [![][rs]][r9] | [![][ns]][n9] | [![][ks]][k9] | [![][as]][a9] | [![][ss]][s9] | | TypeScript support | [![][gts]][g10] | [![][rts]][r10] | [![][nts]][n10] | [![][kts]][k10] | [![][ats]][a10] | [![][sts]][s11] | | Last commit | [![][glc]][g11] | [![][rlc]][r11] | [![][nlc]][n11] | [![][klc]][k11] | [![][alc]][a11] | [![][slc]][s11] | \* It's almost API compatible with the browser `fetch` API.\ \*\* Need to switch the protocol manually. Doesn't accept PUSH streams and doesn't reuse HTTP/2 sessions.\ \*\*\* Currently, only `DownloadProgress` event is supported, `UploadProgress` event is not supported.\ :sparkle: Almost-stable feature, but the API may change. Don't hesitate to try it out!\ :grey_question: Feature in early stage of development. Very experimental. [k0]: https://github.com/sindresorhus/ky [r0]: https://github.com/request/request [n0]: https://github.com/node-fetch/node-fetch [a0]: https://github.com/axios/axios [s0]: https://github.com/visionmedia/superagent [gio]: https://badgen.net/github/open-issues/sindresorhus/got?label [kio]: https://badgen.net/github/open-issues/sindresorhus/ky?label [rio]: https://badgen.net/github/open-issues/request/request?label [nio]: https://badgen.net/github/open-issues/bitinn/node-fetch?label [aio]: https://badgen.net/github/open-issues/axios/axios?label [sio]: https://badgen.net/github/open-issues/visionmedia/superagent?label [g1]: https://github.com/sindresorhus/got/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [k1]: https://github.com/sindresorhus/ky/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [r1]: https://github.com/request/request/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [n1]: https://github.com/bitinn/node-fetch/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [a1]: https://github.com/axios/axios/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [s1]: https://github.com/visionmedia/superagent/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc [gic]: https://badgen.net/github/closed-issues/sindresorhus/got?label [kic]: https://badgen.net/github/closed-issues/sindresorhus/ky?label [ric]: https://badgen.net/github/closed-issues/request/request?label [nic]: https://badgen.net/github/closed-issues/bitinn/node-fetch?label [aic]: https://badgen.net/github/closed-issues/axios/axios?label [sic]: https://badgen.net/github/closed-issues/visionmedia/superagent?label [g2]: https://github.com/sindresorhus/got/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [k2]: https://github.com/sindresorhus/ky/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [r2]: https://github.com/request/request/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [n2]: https://github.com/bitinn/node-fetch/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [a2]: https://github.com/axios/axios/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [s2]: https://github.com/visionmedia/superagent/issues?q=is%3Aissue+is%3Aclosed+sort%3Aupdated-desc [gd]: https://badgen.net/npm/dm/got?label [kd]: https://badgen.net/npm/dm/ky?label [rd]: https://badgen.net/npm/dm/request?label [nd]: https://badgen.net/npm/dm/node-fetch?label [ad]: https://badgen.net/npm/dm/axios?label [sd]: https://badgen.net/npm/dm/superagent?label [g3]: https://www.npmjs.com/package/got [k3]: https://www.npmjs.com/package/ky [r3]: https://www.npmjs.com/package/request [n3]: https://www.npmjs.com/package/node-fetch [a3]: https://www.npmjs.com/package/axios [s3]: https://www.npmjs.com/package/superagent [gc]: https://badgen.net/coveralls/c/github/sindresorhus/got?label [kc]: https://badgen.net/codecov/c/github/sindresorhus/ky?label [rc]: https://badgen.net/coveralls/c/github/request/request?label [nc]: https://badgen.net/coveralls/c/github/bitinn/node-fetch?label [ac]: https://badgen.net/coveralls/c/github/mzabriskie/axios?label [sc]: https://badgen.net/codecov/c/github/visionmedia/superagent?label [g4]: https://coveralls.io/github/sindresorhus/got [k4]: https://codecov.io/gh/sindresorhus/ky [r4]: https://coveralls.io/github/request/request [n4]: https://coveralls.io/github/bitinn/node-fetch [a4]: https://coveralls.io/github/mzabriskie/axios [s4]: https://codecov.io/gh/visionmedia/superagent [gb]: https://badgen.net/travis/sindresorhus/got?label [kb]: https://badgen.net/travis/sindresorhus/ky?label [rb]: https://badgen.net/travis/request/request?label [nb]: https://badgen.net/travis/bitinn/node-fetch?label [ab]: https://badgen.net/travis/axios/axios?label [sb]: https://badgen.net/travis/visionmedia/superagent?label [g5]: https://travis-ci.com/github/sindresorhus/got [k5]: https://travis-ci.com/github/sindresorhus/ky [r5]: https://travis-ci.org/github/request/request [n5]: https://travis-ci.org/github/bitinn/node-fetch [a5]: https://travis-ci.org/github/axios/axios [s5]: https://travis-ci.org/github/visionmedia/superagent [gbg]: https://badgen.net/github/label-issues/sindresorhus/got/bug/open?label [kbg]: https://badgen.net/github/label-issues/sindresorhus/ky/bug/open?label [rbg]: https://badgen.net/github/label-issues/request/request/Needs%20investigation/open?label [nbg]: https://badgen.net/github/label-issues/bitinn/node-fetch/bug/open?label [abg]: https://badgen.net/github/label-issues/axios/axios/type:confirmed%20bug/open?label [sbg]: https://badgen.net/github/label-issues/visionmedia/superagent/Bug/open?label [g6]: https://github.com/sindresorhus/got/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Abug [k6]: https://github.com/sindresorhus/ky/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Abug [r6]: https://github.com/request/request/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A"Needs+investigation" [n6]: https://github.com/bitinn/node-fetch/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Abug [a6]: https://github.com/axios/axios/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22type%3Aconfirmed+bug%22 [s6]: https://github.com/visionmedia/superagent/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3ABug [gdp]: https://badgen.net/npm/dependents/got?label [kdp]: https://badgen.net/npm/dependents/ky?label [rdp]: https://badgen.net/npm/dependents/request?label [ndp]: https://badgen.net/npm/dependents/node-fetch?label [adp]: https://badgen.net/npm/dependents/axios?label [sdp]: https://badgen.net/npm/dependents/superagent?label [g7]: https://www.npmjs.com/package/got?activeTab=dependents [k7]: https://www.npmjs.com/package/ky?activeTab=dependents [r7]: https://www.npmjs.com/package/request?activeTab=dependents [n7]: https://www.npmjs.com/package/node-fetch?activeTab=dependents [a7]: https://www.npmjs.com/package/axios?activeTab=dependents [s7]: https://www.npmjs.com/package/visionmedia?activeTab=dependents [gis]: https://badgen.net/packagephobia/install/got?label [kis]: https://badgen.net/packagephobia/install/ky?label [ris]: https://badgen.net/packagephobia/install/request?label [nis]: https://badgen.net/packagephobia/install/node-fetch?label [ais]: https://badgen.net/packagephobia/install/axios?label [sis]: https://badgen.net/packagephobia/install/superagent?label [g8]: https://packagephobia.now.sh/result?p=got [k8]: https://packagephobia.now.sh/result?p=ky [r8]: https://packagephobia.now.sh/result?p=request [n8]: https://packagephobia.now.sh/result?p=node-fetch [a8]: https://packagephobia.now.sh/result?p=axios [s8]: https://packagephobia.now.sh/result?p=superagent [gs]: https://badgen.net/github/stars/sindresorhus/got?label [ks]: https://badgen.net/github/stars/sindresorhus/ky?label [rs]: https://badgen.net/github/stars/request/request?label [ns]: https://badgen.net/github/stars/bitinn/node-fetch?label [as]: https://badgen.net/github/stars/axios/axios?label [ss]: https://badgen.net/github/stars/visionmedia/superagent?label [g9]: https://github.com/sindresorhus/got [k9]: https://github.com/sindresorhus/ky [r9]: https://github.com/request/request [n9]: https://github.com/node-fetch/node-fetch [a9]: https://github.com/axios/axios [s9]: https://github.com/visionmedia/superagent [gts]: https://badgen.net/npm/types/got?label [kts]: https://badgen.net/npm/types/ky?label [rts]: https://badgen.net/npm/types/request?label [nts]: https://badgen.net/npm/types/node-fetch?label [ats]: https://badgen.net/npm/types/axios?label [sts]: https://badgen.net/npm/types/superagent?label [g10]: https://github.com/sindresorhus/got [k10]: https://github.com/sindresorhus/ky [r10]: https://github.com/request/request [n10]: https://github.com/node-fetch/node-fetch [a10]: https://github.com/axios/axios [s10]: https://github.com/visionmedia/superagent [glc]: https://badgen.net/github/last-commit/sindresorhus/got?label [klc]: https://badgen.net/github/last-commit/sindresorhus/ky?label [rlc]: https://badgen.net/github/last-commit/request/request?label [nlc]: https://badgen.net/github/last-commit/bitinn/node-fetch?label [alc]: https://badgen.net/github/last-commit/axios/axios?label [slc]: https://badgen.net/github/last-commit/visionmedia/superagent?label [g11]: https://github.com/sindresorhus/got/commits [k11]: https://github.com/sindresorhus/ky/commits [r11]: https://github.com/request/request/commits [n11]: https://github.com/node-fetch/node-fetch/commits [a11]: https://github.com/axios/axios/commits [s11]: https://github.com/visionmedia/superagent/commits [Click here][InstallSizeOfTheDependencies] to see the install size of the Got dependencies. [InstallSizeOfTheDependencies]: https://packagephobia.com/result?p=@sindresorhus/is@3.0.0,@szmarczak/http-timer@4.0.5,@types/cacheable-request@6.0.1,@types/responselike@1.0.0,cacheable-lookup@5.0.3,cacheable-request@7.0.1,decompress-response@6.0.0,http2-wrapper@1.0.0,lowercase-keys@2.0.0,p-cancelable@2.0.0,responselike@2.0.0 ## Related - [gh-got](https://github.com/sindresorhus/gh-got) - Got convenience wrapper to interact with the GitHub API - [gl-got](https://github.com/singapore/gl-got) - Got convenience wrapper to interact with the GitLab API - [travis-got](https://github.com/samverschueren/travis-got) - Got convenience wrapper to interact with the Travis API - [graphql-got](https://github.com/kevva/graphql-got) - Got convenience wrapper to interact with GraphQL - [GotQL](https://github.com/khaosdoctor/gotql) - Got convenience wrapper to interact with GraphQL using JSON-parsed queries instead of strings - [got-fetch](https://github.com/alexghr/got-fetch) - Got with a `fetch` interface ## Maintainers [![Sindre Sorhus](https://github.com/sindresorhus.png?size=100)](https://sindresorhus.com) | [![Szymon Marczak](https://github.com/szmarczak.png?size=100)](https://github.com/szmarczak) | [![Giovanni Minotti](https://github.com/Giotino.png?size=100)](https://github.com/Giotino) ---|---|--- [Sindre Sorhus](https://sindresorhus.com) | [Szymon Marczak](https://github.com/szmarczak) | [Giovanni Minotti](https://github.com/Giotino) ###### Former - [Vsevolod Strukchinsky](https://github.com/floatdrop) - [Alexander Tesfamichael](https://github.com/alextes) - [Brandon Smith](https://github.com/brandon93s) - [Luke Childs](https://github.com/lukechilds) ## These amazing companies are using Got                                             
> Segment is a happy user of Got! Got powers the main backend API that our app talks to. It's used by our in-house RPC client that we use to communicate with all microservices. > > — Vadim Demedes > Antora, a static site generator for creating documentation sites, uses Got to download the UI bundle. In Antora, the UI bundle (aka theme) is maintained as a separate project. That project exports the UI as a zip file we call the UI bundle. The main site generator downloads that UI from a URL using Got and streams it to vinyl-zip to extract the files. Those files go on to be used to create the HTML pages and supporting assets. > > — Dan Allen > GetVoIP is happily using Got in production. One of the unique capabilities of Got is the ability to handle Unix sockets which enables us to build a full control interfaces for our docker stack. > > — Daniel Kalen > We're using Got inside of Exoframe to handle all the communication between CLI and server. Exoframe is a self-hosted tool that allows simple one-command deployments using Docker. > > — Tim Ermilov > Karaoke Mugen uses Got to fetch content updates from its online server. > > — Axel Terizaki > Renovate uses Got, gh-got and gl-got to send millions of queries per day to GitHub, GitLab, npmjs, PyPi, Packagist, Docker Hub, Terraform, CircleCI, and more. > > — Rhys Arkins > Resistbot uses Got to communicate from the API frontend where all correspondence ingresses to the officials lookup database in back. > > — Chris Erickson > Natural Cycles is using Got to communicate with all kinds of 3rd-party REST APIs (over 9000!). > > — Kirill Groshkov > Microlink is a cloud browser as an API service that uses Got widely as the main HTTP client, serving ~22M requests a month, every time a network call needs to be performed. > > — Kiko Beats > We’re using Got at Radity. Thanks for such an amazing work! > > — Mirzayev Farid ## For enterprise Available as part of the Tidelift Subscription. The maintainers of `got` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source dependencies you use to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use. [Learn more.](https://tidelift.com/subscription/pkg/npm-got?utm_source=npm-got&utm_medium=referral&utm_campaign=enterprise&utm_term=repo) got-11.8.5/source/000077500000000000000000000000001424347352000137035ustar00rootroot00000000000000got-11.8.5/source/as-promise/000077500000000000000000000000001424347352000157625ustar00rootroot00000000000000got-11.8.5/source/as-promise/create-rejection.ts000066400000000000000000000014751424347352000215640ustar00rootroot00000000000000import {CancelableRequest, BeforeErrorHook, RequestError} from './types'; export default function createRejection(error: Error, ...beforeErrorGroups: Array): CancelableRequest { const promise = (async () => { if (error instanceof RequestError) { try { for (const hooks of beforeErrorGroups) { if (hooks) { for (const hook of hooks) { // eslint-disable-next-line no-await-in-loop error = await hook(error as RequestError); } } } } catch (error_) { error = error_; } } throw error; })() as CancelableRequest; const returnPromise = (): CancelableRequest => promise; promise.json = returnPromise; promise.text = returnPromise; promise.buffer = returnPromise; promise.on = returnPromise; return promise; } got-11.8.5/source/as-promise/index.ts000066400000000000000000000127571424347352000174550ustar00rootroot00000000000000import {EventEmitter} from 'events'; import is from '@sindresorhus/is'; import PCancelable = require('p-cancelable'); import { NormalizedOptions, CancelableRequest, Response, RequestError, HTTPError, CancelError } from './types'; import parseBody from './parse-body'; import Request from '../core'; import proxyEvents from '../core/utils/proxy-events'; import getBuffer from '../core/utils/get-buffer'; import {isResponseOk} from '../core/utils/is-response-ok'; const proxiedRequestEvents = [ 'request', 'response', 'redirect', 'uploadProgress', 'downloadProgress' ]; export default function asPromise(normalizedOptions: NormalizedOptions): CancelableRequest { let globalRequest: Request; let globalResponse: Response; const emitter = new EventEmitter(); const promise = new PCancelable((resolve, reject, onCancel) => { const makeRequest = (retryCount: number): void => { const request = new Request(undefined, normalizedOptions); request.retryCount = retryCount; request._noPipe = true; onCancel(() => request.destroy()); onCancel.shouldReject = false; onCancel(() => reject(new CancelError(request))); globalRequest = request; request.once('response', async (response: Response) => { response.retryCount = retryCount; if (response.request.aborted) { // Canceled while downloading - will throw a `CancelError` or `TimeoutError` error return; } // Download body let rawBody; try { rawBody = await getBuffer(request); response.rawBody = rawBody; } catch { // The same error is caught below. // See request.once('error') return; } if (request._isAboutToError) { return; } // Parse body const contentEncoding = (response.headers['content-encoding'] ?? '').toLowerCase(); const isCompressed = ['gzip', 'deflate', 'br'].includes(contentEncoding); const {options} = request; if (isCompressed && !options.decompress) { response.body = rawBody; } else { try { response.body = parseBody(response, options.responseType, options.parseJson, options.encoding); } catch (error) { // Fallback to `utf8` response.body = rawBody.toString(); if (isResponseOk(response)) { request._beforeError(error); return; } } } try { for (const [index, hook] of options.hooks.afterResponse.entries()) { // @ts-expect-error TS doesn't notice that CancelableRequest is a Promise // eslint-disable-next-line no-await-in-loop response = await hook(response, async (updatedOptions): CancelableRequest => { const typedOptions = Request.normalizeArguments(undefined, { ...updatedOptions, retry: { calculateDelay: () => 0 }, throwHttpErrors: false, resolveBodyOnly: false }, options); // Remove any further hooks for that request, because we'll call them anyway. // The loop continues. We don't want duplicates (asPromise recursion). typedOptions.hooks.afterResponse = typedOptions.hooks.afterResponse.slice(0, index); for (const hook of typedOptions.hooks.beforeRetry) { // eslint-disable-next-line no-await-in-loop await hook(typedOptions); } const promise: CancelableRequest = asPromise(typedOptions); onCancel(() => { promise.catch(() => {}); promise.cancel(); }); return promise; }); } } catch (error) { request._beforeError(new RequestError(error.message, error, request)); return; } globalResponse = response; if (!isResponseOk(response)) { request._beforeError(new HTTPError(response)); return; } resolve(request.options.resolveBodyOnly ? response.body as T : response as unknown as T); }); const onError = (error: RequestError) => { if (promise.isCanceled) { return; } const {options} = request; if (error instanceof HTTPError && !options.throwHttpErrors) { const {response} = error; resolve(request.options.resolveBodyOnly ? response.body as T : response as unknown as T); return; } reject(error); }; request.once('error', onError); const previousBody = request.options.body; request.once('retry', (newRetryCount: number, error: RequestError) => { if (previousBody === error.request?.options.body && is.nodeStream(error.request?.options.body)) { onError(error); return; } makeRequest(newRetryCount); }); proxyEvents(request, emitter, proxiedRequestEvents); }; makeRequest(0); }) as CancelableRequest; promise.on = (event: string, fn: (...args: any[]) => void) => { emitter.on(event, fn); return promise; }; const shortcut = (responseType: NormalizedOptions['responseType']): CancelableRequest => { const newPromise = (async () => { // Wait until downloading has ended await promise; const {options} = globalResponse.request; return parseBody(globalResponse, responseType, options.parseJson, options.encoding); })(); Object.defineProperties(newPromise, Object.getOwnPropertyDescriptors(promise)); return newPromise as CancelableRequest; }; promise.json = () => { const {headers} = globalRequest.options; if (!globalRequest.writableFinished && headers.accept === undefined) { headers.accept = 'application/json'; } return shortcut('json'); }; promise.buffer = () => shortcut('buffer'); promise.text = () => shortcut('text'); return promise; } export * from './types'; got-11.8.5/source/as-promise/normalize-arguments.ts000066400000000000000000000052321424347352000223370ustar00rootroot00000000000000import is, {assert} from '@sindresorhus/is'; import { Options, NormalizedOptions, Defaults, Method } from './types'; const normalizeArguments = (options: NormalizedOptions, defaults?: Defaults): NormalizedOptions => { if (is.null_(options.encoding)) { throw new TypeError('To get a Buffer, set `options.responseType` to `buffer` instead'); } assert.any([is.string, is.undefined], options.encoding); assert.any([is.boolean, is.undefined], options.resolveBodyOnly); assert.any([is.boolean, is.undefined], options.methodRewriting); assert.any([is.boolean, is.undefined], options.isStream); assert.any([is.string, is.undefined], options.responseType); // `options.responseType` if (options.responseType === undefined) { options.responseType = 'text'; } // `options.retry` const {retry} = options; if (defaults) { options.retry = {...defaults.retry}; } else { options.retry = { calculateDelay: retryObject => retryObject.computedValue, limit: 0, methods: [], statusCodes: [], errorCodes: [], maxRetryAfter: undefined }; } if (is.object(retry)) { options.retry = { ...options.retry, ...retry }; options.retry.methods = [...new Set(options.retry.methods.map(method => method.toUpperCase() as Method))]; options.retry.statusCodes = [...new Set(options.retry.statusCodes)]; options.retry.errorCodes = [...new Set(options.retry.errorCodes)]; } else if (is.number(retry)) { options.retry.limit = retry; } if (is.undefined(options.retry.maxRetryAfter)) { options.retry.maxRetryAfter = Math.min( // TypeScript is not smart enough to handle `.filter(x => is.number(x))`. // eslint-disable-next-line unicorn/no-fn-reference-in-iterator ...[options.timeout.request, options.timeout.connect].filter(is.number) ); } // `options.pagination` if (is.object(options.pagination)) { if (defaults) { (options as Options).pagination = { ...defaults.pagination, ...options.pagination }; } const {pagination} = options; if (!is.function_(pagination.transform)) { throw new Error('`options.pagination.transform` must be implemented'); } if (!is.function_(pagination.shouldContinue)) { throw new Error('`options.pagination.shouldContinue` must be implemented'); } if (!is.function_(pagination.filter)) { throw new TypeError('`options.pagination.filter` must be implemented'); } if (!is.function_(pagination.paginate)) { throw new Error('`options.pagination.paginate` must be implemented'); } } // JSON mode if (options.responseType === 'json' && options.headers.accept === undefined) { options.headers.accept = 'application/json'; } return options; }; export default normalizeArguments; got-11.8.5/source/as-promise/parse-body.ts000066400000000000000000000013131424347352000203750ustar00rootroot00000000000000import { ResponseType, ParseError, Response, ParseJsonFunction } from './types'; const parseBody = (response: Response, responseType: ResponseType, parseJson: ParseJsonFunction, encoding?: BufferEncoding): unknown => { const {rawBody} = response; try { if (responseType === 'text') { return rawBody.toString(encoding); } if (responseType === 'json') { return rawBody.length === 0 ? '' : parseJson(rawBody.toString()); } if (responseType === 'buffer') { return rawBody; } throw new ParseError({ message: `Unknown body type '${responseType as string}'`, name: 'Error' }, response); } catch (error) { throw new ParseError(error, response); } }; export default parseBody; got-11.8.5/source/as-promise/types.ts000066400000000000000000000201171424347352000174770ustar00rootroot00000000000000import PCancelable = require('p-cancelable'); import Request, { Options, Response, RequestError, RequestEvents } from '../core'; /** All parsing methods supported by Got. */ export type ResponseType = 'json' | 'buffer' | 'text'; export interface PaginationOptions { /** All options accepted by `got.paginate()`. */ pagination?: { /** A function that transform [`Response`](#response) into an array of items. This is where you should do the parsing. @default response => JSON.parse(response.body) */ transform?: (response: Response) => Promise | T[]; /** Checks whether the item should be emitted or not. @default (item, allItems, currentItems) => true */ filter?: (item: T, allItems: T[], currentItems: T[]) => boolean; /** The function takes three arguments: - `response` - The current response object. - `allItems` - An array of the emitted items. - `currentItems` - Items from the current response. It should return an object representing Got options pointing to the next page. The options are merged automatically with the previous request, therefore the options returned `pagination.paginate(...)` must reflect changes only. If there are no more pages, `false` should be returned. @example ``` const got = require('got'); (async () => { const limit = 10; const items = got.paginate('https://example.com/items', { searchParams: { limit, offset: 0 }, pagination: { paginate: (response, allItems, currentItems) => { const previousSearchParams = response.request.options.searchParams; const previousOffset = previousSearchParams.get('offset'); if (currentItems.length < limit) { return false; } return { searchParams: { ...previousSearchParams, offset: Number(previousOffset) + limit, } }; } } }); console.log('Items from all pages:', items); })(); ``` */ paginate?: (response: Response, allItems: T[], currentItems: T[]) => Options | false; /** Checks whether the pagination should continue. For example, if you need to stop **before** emitting an entry with some flag, you should use `(item, allItems, currentItems) => !item.flag`. If you want to stop **after** emitting the entry, you should use `(item, allItems, currentItems) => allItems.some(entry => entry.flag)` instead. @default (item, allItems, currentItems) => true */ shouldContinue?: (item: T, allItems: T[], currentItems: T[]) => boolean; /** The maximum amount of items that should be emitted. @default Infinity */ countLimit?: number; /** Milliseconds to wait before the next request is triggered. @default 0 */ backoff?: number; /** The maximum amount of request that should be triggered. Retries on failure are not counted towards this limit. For example, it can be helpful during development to avoid an infinite number of requests. @default 10000 */ requestLimit?: number; /** Defines how the parameter `allItems` in pagination.paginate, pagination.filter and pagination.shouldContinue is managed. When set to `false`, the parameter `allItems` is always an empty array. This option can be helpful to save on memory usage when working with a large dataset. */ stackAllItems?: boolean; }; } export type AfterResponseHook = (response: Response, retryWithMergedOptions: (options: Options) => CancelableRequest) => Response | CancelableRequest | Promise>; // These should be merged into Options in core/index.ts export namespace PromiseOnly { export interface Hooks { /** Called with [response object](#response) and a retry function. Calling the retry function will trigger `beforeRetry` hooks. Each function should return the response. This is especially useful when you want to refresh an access token. __Note__: When using streams, this hook is ignored. @example ``` const got = require('got'); const instance = got.extend({ hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { // Unauthorized const updatedOptions = { headers: { token: getNewToken() // Refresh the access token } }; // Save for further requests instance.defaults.options = got.mergeOptions(instance.defaults.options, updatedOptions); // Make a new retry return retryWithMergedOptions(updatedOptions); } // No changes otherwise return response; } ], beforeRetry: [ (options, error, retryCount) => { // This will be called on `retryWithMergedOptions(...)` } ] }, mutableDefaults: true }); ``` */ afterResponse?: AfterResponseHook[]; } export interface Options extends PaginationOptions { /** The parsing method. The promise also has `.text()`, `.json()` and `.buffer()` methods which return another Got promise for the parsed body. It's like setting the options to `{responseType: 'json', resolveBodyOnly: true}` but without affecting the main Got promise. __Note__: When using streams, this option is ignored. @example ``` (async () => { const responsePromise = got(url); const bufferPromise = responsePromise.buffer(); const jsonPromise = responsePromise.json(); const [response, buffer, json] = Promise.all([responsePromise, bufferPromise, jsonPromise]); // `response` is an instance of Got Response // `buffer` is an instance of Buffer // `json` is an object })(); ``` @example ``` // This const body = await got(url).json(); // is semantically the same as this const body = await got(url, {responseType: 'json', resolveBodyOnly: true}); ``` */ responseType?: ResponseType; /** When set to `true` the promise will return the Response body instead of the Response object. @default false */ resolveBodyOnly?: boolean; /** Returns a `Stream` instead of a `Promise`. This is equivalent to calling `got.stream(url, options?)`. @default false */ isStream?: boolean; /** [Encoding](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) to be used on `setEncoding` of the response data. To get a [`Buffer`](https://nodejs.org/api/buffer.html), you need to set `responseType` to `buffer` instead. Don't set this option to `null`. __Note__: This doesn't affect streams! Instead, you need to do `got.stream(...).setEncoding(encoding)`. @default 'utf-8' */ encoding?: BufferEncoding; } export interface NormalizedOptions { responseType: ResponseType; resolveBodyOnly: boolean; isStream: boolean; encoding?: BufferEncoding; pagination?: Required['pagination']>; } export interface Defaults { responseType: ResponseType; resolveBodyOnly: boolean; isStream: boolean; pagination?: Required['pagination']>; } export type HookEvent = 'afterResponse'; } /** An error to be thrown when server response code is 2xx, and parsing body fails. Includes a `response` property. */ export class ParseError extends RequestError { declare readonly response: Response; constructor(error: Error, response: Response) { const {options} = response.request; super(`${error.message} in "${options.url.toString()}"`, error, response.request); this.name = 'ParseError'; this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_BODY_PARSE_FAILURE' : this.code; } } /** An error to be thrown when the request is aborted with `.cancel()`. */ export class CancelError extends RequestError { declare readonly response: Response; constructor(request: Request) { super('Promise was canceled', {}, request); this.name = 'CancelError'; this.code = 'ERR_CANCELED'; } get isCanceled() { return true; } } export interface CancelableRequest extends PCancelable, RequestEvents> { json: () => CancelableRequest; buffer: () => CancelableRequest; text: () => CancelableRequest; } export * from '../core'; got-11.8.5/source/core/000077500000000000000000000000001424347352000146335ustar00rootroot00000000000000got-11.8.5/source/core/calculate-retry-delay.ts000066400000000000000000000020561424347352000214020ustar00rootroot00000000000000import {RetryFunction} from '.'; type Returns unknown, V> = (...args: Parameters) => V; export const retryAfterStatusCodes: ReadonlySet = new Set([413, 429, 503]); const calculateRetryDelay: Returns = ({attemptCount, retryOptions, error, retryAfter}) => { if (attemptCount > retryOptions.limit) { return 0; } const hasMethod = retryOptions.methods.includes(error.options.method); const hasErrorCode = retryOptions.errorCodes.includes(error.code); const hasStatusCode = error.response && retryOptions.statusCodes.includes(error.response.statusCode); if (!hasMethod || (!hasErrorCode && !hasStatusCode)) { return 0; } if (error.response) { if (retryAfter) { if (retryOptions.maxRetryAfter === undefined || retryAfter > retryOptions.maxRetryAfter) { return 0; } return retryAfter; } if (error.response.statusCode === 413) { return 0; } } const noise = Math.random() * 100; return ((2 ** (attemptCount - 1)) * 1000) + noise; }; export default calculateRetryDelay; got-11.8.5/source/core/index.ts000066400000000000000000002466151424347352000163300ustar00rootroot00000000000000import {promisify} from 'util'; import {Duplex, Writable, Readable} from 'stream'; import {ReadStream} from 'fs'; import {URL, URLSearchParams} from 'url'; import {Socket} from 'net'; import {SecureContextOptions, DetailedPeerCertificate} from 'tls'; import http = require('http'); import {ClientRequest, RequestOptions, IncomingMessage, ServerResponse, request as httpRequest} from 'http'; import https = require('https'); import timer, {ClientRequestWithTimings, Timings, IncomingMessageWithTimings} from '@szmarczak/http-timer'; import CacheableLookup from 'cacheable-lookup'; import CacheableRequest = require('cacheable-request'); import decompressResponse = require('decompress-response'); // @ts-expect-error Missing types import http2wrapper = require('http2-wrapper'); import lowercaseKeys = require('lowercase-keys'); import ResponseLike = require('responselike'); import is, {assert} from '@sindresorhus/is'; import getBodySize from './utils/get-body-size'; import isFormData from './utils/is-form-data'; import proxyEvents from './utils/proxy-events'; import timedOut, {Delays, TimeoutError as TimedOutTimeoutError} from './utils/timed-out'; import urlToOptions from './utils/url-to-options'; import optionsToUrl, {URLOptions} from './utils/options-to-url'; import WeakableMap from './utils/weakable-map'; import getBuffer from './utils/get-buffer'; import {DnsLookupIpVersion, isDnsLookupIpVersion, dnsLookupIpVersionToFamily} from './utils/dns-ip-version'; import {isResponseOk} from './utils/is-response-ok'; import deprecationWarning from '../utils/deprecation-warning'; import normalizePromiseArguments from '../as-promise/normalize-arguments'; import {PromiseOnly} from '../as-promise/types'; import calculateRetryDelay from './calculate-retry-delay'; let globalDnsCache: CacheableLookup; type HttpRequestFunction = typeof httpRequest; type Error = NodeJS.ErrnoException; const kRequest = Symbol('request'); const kResponse = Symbol('response'); const kResponseSize = Symbol('responseSize'); const kDownloadedSize = Symbol('downloadedSize'); const kBodySize = Symbol('bodySize'); const kUploadedSize = Symbol('uploadedSize'); const kServerResponsesPiped = Symbol('serverResponsesPiped'); const kUnproxyEvents = Symbol('unproxyEvents'); const kIsFromCache = Symbol('isFromCache'); const kCancelTimeouts = Symbol('cancelTimeouts'); const kStartedReading = Symbol('startedReading'); const kStopReading = Symbol('stopReading'); const kTriggerRead = Symbol('triggerRead'); const kBody = Symbol('body'); const kJobs = Symbol('jobs'); const kOriginalResponse = Symbol('originalResponse'); const kRetryTimeout = Symbol('retryTimeout'); export const kIsNormalizedAlready = Symbol('isNormalizedAlready'); const supportsBrotli = is.string((process.versions as any).brotli); export interface Agents { http?: http.Agent; https?: https.Agent; http2?: unknown; } export const withoutBody: ReadonlySet = new Set(['GET', 'HEAD']); export interface ToughCookieJar { getCookieString: ((currentUrl: string, options: Record, cb: (err: Error | null, cookies: string) => void) => void) & ((url: string, callback: (error: Error | null, cookieHeader: string) => void) => void); setCookie: ((cookieOrString: unknown, currentUrl: string, options: Record, cb: (err: Error | null, cookie: unknown) => void) => void) & ((rawCookie: string, url: string, callback: (error: Error | null, result: unknown) => void) => void); } export interface PromiseCookieJar { getCookieString: (url: string) => Promise; setCookie: (rawCookie: string, url: string) => Promise; } /** All available HTTP request methods provided by Got. */ export type Method = | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'HEAD' | 'DELETE' | 'OPTIONS' | 'TRACE' | 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options' | 'trace'; type Promisable = T | Promise; export type InitHook = (options: Options) => void; export type BeforeRequestHook = (options: NormalizedOptions) => Promisable; export type BeforeRedirectHook = (options: NormalizedOptions, response: Response) => Promisable; export type BeforeErrorHook = (error: RequestError) => Promisable; export type BeforeRetryHook = (options: NormalizedOptions, error?: RequestError, retryCount?: number) => void | Promise; interface PlainHooks { /** Called with plain request options, right before their normalization. This is especially useful in conjunction with `got.extend()` when the input needs custom handling. __Note #1__: This hook must be synchronous! __Note #2__: Errors in this hook will be converted into an instances of `RequestError`. __Note #3__: The options object may not have a `url` property. To modify it, use a `beforeRequest` hook instead. @default [] */ init?: InitHook[]; /** Called with normalized request options. Got will make no further changes to the request before it is sent. This is especially useful in conjunction with `got.extend()` when you want to create an API client that, for example, uses HMAC-signing. @default [] */ beforeRequest?: BeforeRequestHook[]; /** Called with normalized request options and the redirect response. Got will make no further changes to the request. This is especially useful when you want to avoid dead sites. @default [] @example ``` const got = require('got'); got('https://example.com', { hooks: { beforeRedirect: [ (options, response) => { if (options.hostname === 'deadSite') { options.hostname = 'fallbackSite'; } } ] } }); ``` */ beforeRedirect?: BeforeRedirectHook[]; /** Called with an `Error` instance. The error is passed to the hook right before it's thrown. This is especially useful when you want to have more detailed errors. __Note__: Errors thrown while normalizing input options are thrown directly and not part of this hook. @default [] @example ``` const got = require('got'); got('https://api.github.com/some-endpoint', { hooks: { beforeError: [ error => { const {response} = error; if (response && response.body) { error.name = 'GitHubError'; error.message = `${response.body.message} (${response.statusCode})`; } return error; } ] } }); ``` */ beforeError?: BeforeErrorHook[]; /** Called with normalized request options, the error and the retry count. Got will make no further changes to the request. This is especially useful when some extra work is required before the next try. __Note__: When using streams, this hook is ignored. __Note__: When retrying in a `afterResponse` hook, all remaining `beforeRetry` hooks will be called without the `error` and `retryCount` arguments. @default [] @example ``` const got = require('got'); got.post('https://example.com', { hooks: { beforeRetry: [ (options, error, retryCount) => { if (error.response.statusCode === 413) { // Payload too large options.body = getNewBody(); } } ] } }); ``` */ beforeRetry?: BeforeRetryHook[]; } /** All available hook of Got. */ export interface Hooks extends PromiseOnly.Hooks, PlainHooks {} type PlainHookEvent = 'init' | 'beforeRequest' | 'beforeRedirect' | 'beforeError' | 'beforeRetry'; /** All hook events acceptable by Got. */ export type HookEvent = PromiseOnly.HookEvent | PlainHookEvent; export const knownHookEvents: HookEvent[] = [ 'init', 'beforeRequest', 'beforeRedirect', 'beforeError', 'beforeRetry', // Promise-Only 'afterResponse' ]; type AcceptableResponse = IncomingMessageWithTimings | ResponseLike; type AcceptableRequestResult = AcceptableResponse | ClientRequest | Promise | undefined; export type RequestFunction = (url: URL, options: RequestOptions, callback?: (response: AcceptableResponse) => void) => AcceptableRequestResult; export type Headers = Record; type CacheableRequestFunction = ( options: string | URL | RequestOptions, cb?: (response: ServerResponse | ResponseLike) => void ) => CacheableRequest.Emitter; type CheckServerIdentityFunction = (hostname: string, certificate: DetailedPeerCertificate) => Error | void; export type ParseJsonFunction = (text: string) => unknown; export type StringifyJsonFunction = (object: unknown) => string; interface RealRequestOptions extends https.RequestOptions { checkServerIdentity: CheckServerIdentityFunction; } export interface RetryObject { attemptCount: number; retryOptions: RequiredRetryOptions; error: TimeoutError | RequestError; computedValue: number; retryAfter?: number; } export type RetryFunction = (retryObject: RetryObject) => number | Promise; /** An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes. Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1). The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value. The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry). By default, it retries *only* on the specified methods, status codes, and on these network errors: - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached. - `ECONNRESET`: Connection was forcibly closed by a peer. - `EADDRINUSE`: Could not bind to any free port. - `ECONNREFUSED`: Connection was refused by the server. - `EPIPE`: The remote side of the stream being written has been closed. - `ENOTFOUND`: Couldn't resolve the hostname to an IP address. - `ENETUNREACH`: No internet connection. - `EAI_AGAIN`: DNS lookup timed out. __Note__: If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. __Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request. */ export interface RequiredRetryOptions { limit: number; methods: Method[]; statusCodes: number[]; errorCodes: string[]; calculateDelay: RetryFunction; maxRetryAfter?: number; } export interface CacheOptions { shared?: boolean; cacheHeuristic?: number; immutableMinTimeToLive?: number; ignoreCargoCult?: boolean; } interface PlainOptions extends URLOptions { /** Custom request function. The main purpose of this is to [support HTTP2 using a wrapper](https://github.com/szmarczak/http2-wrapper). @default http.request | https.request */ request?: RequestFunction; /** An object representing `http`, `https` and `http2` keys for [`http.Agent`](https://nodejs.org/api/http.html#http_class_http_agent), [`https.Agent`](https://nodejs.org/api/https.html#https_class_https_agent) and [`http2wrapper.Agent`](https://github.com/szmarczak/http2-wrapper#new-http2agentoptions) instance. This is necessary because a request to one protocol might redirect to another. In such a scenario, Got will switch over to the right protocol agent for you. If a key is not present, it will default to a global agent. @example ``` const got = require('got'); const HttpAgent = require('agentkeepalive'); const {HttpsAgent} = HttpAgent; got('https://sindresorhus.com', { agent: { http: new HttpAgent(), https: new HttpsAgent() } }); ``` */ agent?: Agents | false; /** Decompress the response automatically. This will set the `accept-encoding` header to `gzip, deflate, br` on Node.js 11.7.0+ or `gzip, deflate` for older Node.js versions, unless you set it yourself. Brotli (`br`) support requires Node.js 11.7.0 or later. If this is disabled, a compressed response is returned as a `Buffer`. This may be useful if you want to handle decompression yourself or stream the raw compressed data. @default true */ decompress?: boolean; /** Milliseconds to wait for the server to end the response before aborting the request with `got.TimeoutError` error (a.k.a. `request` property). By default, there's no timeout. This also accepts an `object` with the following fields to constrain the duration of each phase of the request lifecycle: - `lookup` starts when a socket is assigned and ends when the hostname has been resolved. Does not apply when using a Unix domain socket. - `connect` starts when `lookup` completes (or when the socket is assigned if lookup does not apply to the request) and ends when the socket is connected. - `secureConnect` starts when `connect` completes and ends when the handshaking process completes (HTTPS only). - `socket` starts when the socket is connected. See [request.setTimeout](https://nodejs.org/api/http.html#http_request_settimeout_timeout_callback). - `response` starts when the request has been written to the socket and ends when the response headers are received. - `send` starts when the socket is connected and ends with the request has been written to the socket. - `request` starts when the request is initiated and ends when the response's end event fires. */ timeout?: Delays | number; /** When specified, `prefixUrl` will be prepended to `url`. The prefix can be any valid URL, either relative or absolute. A trailing slash `/` is optional - one will be added automatically. __Note__: `prefixUrl` will be ignored if the `url` argument is a URL instance. __Note__: Leading slashes in `input` are disallowed when using this option to enforce consistency and avoid confusion. For example, when the prefix URL is `https://example.com/foo` and the input is `/bar`, there's ambiguity whether the resulting URL would become `https://example.com/foo/bar` or `https://example.com/bar`. The latter is used by browsers. __Tip__: Useful when used with `got.extend()` to create niche-specific Got instances. __Tip__: You can change `prefixUrl` using hooks as long as the URL still includes the `prefixUrl`. If the URL doesn't include it anymore, it will throw. @example ``` const got = require('got'); (async () => { await got('unicorn', {prefixUrl: 'https://cats.com'}); //=> 'https://cats.com/unicorn' const instance = got.extend({ prefixUrl: 'https://google.com' }); await instance('unicorn', { hooks: { beforeRequest: [ options => { options.prefixUrl = 'https://cats.com'; } ] } }); //=> 'https://cats.com/unicorn' })(); ``` */ prefixUrl?: string | URL; /** __Note #1__: The `body` option cannot be used with the `json` or `form` option. __Note #2__: If you provide this option, `got.stream()` will be read-only. __Note #3__: If you provide a payload with the `GET` or `HEAD` method, it will throw a `TypeError` unless the method is `GET` and the `allowGetBody` option is set to `true`. __Note #4__: This option is not enumerable and will not be merged with the instance defaults. The `content-length` header will be automatically set if `body` is a `string` / `Buffer` / `fs.createReadStream` instance / [`form-data` instance](https://github.com/form-data/form-data), and `content-length` and `transfer-encoding` are not manually set in `options.headers`. */ body?: string | Buffer | Readable; /** The form body is converted to a query string using [`(new URLSearchParams(object)).toString()`](https://nodejs.org/api/url.html#url_constructor_new_urlsearchparams_obj). If the `Content-Type` header is not present, it will be set to `application/x-www-form-urlencoded`. __Note #1__: If you provide this option, `got.stream()` will be read-only. __Note #2__: This option is not enumerable and will not be merged with the instance defaults. */ form?: Record; /** JSON body. If the `Content-Type` header is not set, it will be set to `application/json`. __Note #1__: If you provide this option, `got.stream()` will be read-only. __Note #2__: This option is not enumerable and will not be merged with the instance defaults. */ json?: Record; /** The URL to request, as a string, a [`https.request` options object](https://nodejs.org/api/https.html#https_https_request_options_callback), or a [WHATWG `URL`](https://nodejs.org/api/url.html#url_class_url). Properties from `options` will override properties in the parsed `url`. If no protocol is specified, it will throw a `TypeError`. __Note__: The query string is **not** parsed as search params. @example ``` got('https://example.com/?query=a b'); //=> https://example.com/?query=a%20b got('https://example.com/', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b // The query string is overridden by `searchParams` got('https://example.com/?query=a b', {searchParams: {query: 'a b'}}); //=> https://example.com/?query=a+b ``` */ url?: string | URL; /** Cookie support. You don't have to care about parsing or how to store them. __Note__: If you provide this option, `options.headers.cookie` will be overridden. */ cookieJar?: PromiseCookieJar | ToughCookieJar; /** Ignore invalid cookies instead of throwing an error. Only useful when the `cookieJar` option has been set. Not recommended. @default false */ ignoreInvalidCookies?: boolean; /** Query string that will be added to the request URL. This will override the query string in `url`. If you need to pass in an array, you can do it using a `URLSearchParams` instance. @example ``` const got = require('got'); const searchParams = new URLSearchParams([['key', 'a'], ['key', 'b']]); got('https://example.com', {searchParams}); console.log(searchParams.toString()); //=> 'key=a&key=b' ``` */ searchParams?: string | Record | URLSearchParams; /** An instance of [`CacheableLookup`](https://github.com/szmarczak/cacheable-lookup) used for making DNS lookups. Useful when making lots of requests to different *public* hostnames. `CacheableLookup` uses `dns.resolver4(..)` and `dns.resolver6(...)` under the hood and fall backs to `dns.lookup(...)` when the first two fail, which may lead to additional delay. __Note__: This should stay disabled when making requests to internal hostnames such as `localhost`, `database.local` etc. @default false */ dnsCache?: CacheableLookup | boolean; /** User data. In contrast to other options, `context` is not enumerable. __Note__: The object is never merged, it's just passed through. Got will not modify the object in any way. @example ``` const got = require('got'); const instance = got.extend({ hooks: { beforeRequest: [ options => { if (!options.context || !options.context.token) { throw new Error('Token required'); } options.headers.token = options.context.token; } ] } }); (async () => { const context = { token: 'secret' }; const response = await instance('https://httpbin.org/headers', {context}); // Let's see the headers console.log(response.body); })(); ``` */ context?: Record; /** Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially. */ hooks?: Hooks; /** Defines if redirect responses should be followed automatically. Note that if a `303` is sent by the server in response to any request type (`POST`, `DELETE`, etc.), Got will automatically request the resource pointed to in the location header via `GET`. This is in accordance with [the spec](https://tools.ietf.org/html/rfc7231#section-6.4.4). @default true */ followRedirect?: boolean; /** If exceeded, the request will be aborted and a `MaxRedirectsError` will be thrown. @default 10 */ maxRedirects?: number; /** A cache adapter instance for storing cached response data. @default false */ cache?: string | CacheableRequest.StorageAdapter | false; /** Determines if a `got.HTTPError` is thrown for unsuccessful responses. If this is disabled, requests that encounter an error status code will be resolved with the `response` instead of throwing. This may be useful if you are checking for resource availability and are expecting error responses. @default true */ throwHttpErrors?: boolean; username?: string; password?: string; /** If set to `true`, Got will additionally accept HTTP2 requests. It will choose either HTTP/1.1 or HTTP/2 depending on the ALPN protocol. __Note__: Overriding `options.request` will disable HTTP2 support. __Note__: This option will default to `true` in the next upcoming major release. @default false @example ``` const got = require('got'); (async () => { const {headers} = await got('https://nghttp2.org/httpbin/anything', {http2: true}); console.log(headers.via); //=> '2 nghttpx' })(); ``` */ http2?: boolean; /** Set this to `true` to allow sending body for the `GET` method. However, the [HTTP/2 specification](https://tools.ietf.org/html/rfc7540#section-8.1.3) says that `An HTTP GET request includes request header fields and no payload body`, therefore when using the HTTP/2 protocol this option will have no effect. This option is only meant to interact with non-compliant servers when you have no other choice. __Note__: The [RFC 7321](https://tools.ietf.org/html/rfc7231#section-4.3.1) doesn't specify any particular behavior for the GET method having a payload, therefore __it's considered an [anti-pattern](https://en.wikipedia.org/wiki/Anti-pattern)__. @default false */ allowGetBody?: boolean; lookup?: CacheableLookup['lookup']; /** Request headers. Existing headers will be overwritten. Headers set to `undefined` will be omitted. @default {} */ headers?: Headers; /** By default, redirects will use [method rewriting](https://tools.ietf.org/html/rfc7231#section-6.4). For example, when sending a POST request and receiving a `302`, it will resend the body to the new location using the same HTTP method (`POST` in this case). @default true */ methodRewriting?: boolean; /** Indicates which DNS record family to use. Values: - `auto`: IPv4 (if present) or IPv6 - `ipv4`: Only IPv4 - `ipv6`: Only IPv6 __Note__: If you are using the undocumented option `family`, `dnsLookupIpVersion` will override it. @default 'auto' */ dnsLookupIpVersion?: DnsLookupIpVersion; /** A function used to parse JSON responses. @example ``` const got = require('got'); const Bourne = require('@hapi/bourne'); (async () => { const parsed = await got('https://example.com', { parseJson: text => Bourne.parse(text) }).json(); console.log(parsed); })(); ``` */ parseJson?: ParseJsonFunction; /** A function used to stringify the body of JSON requests. @example ``` const got = require('got'); (async () => { await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (key.startsWith('_')) { return; } return value; }), json: { some: 'payload', _ignoreMe: 1234 } }); })(); ``` @example ``` const got = require('got'); (async () => { await got.post('https://example.com', { stringifyJson: object => JSON.stringify(object, (key, value) => { if (typeof value === 'number') { return value.toString(); } return value; }), json: { some: 'payload', number: 1 } }); })(); ``` */ stringifyJson?: StringifyJsonFunction; /** An object representing `limit`, `calculateDelay`, `methods`, `statusCodes`, `maxRetryAfter` and `errorCodes` fields for maximum retry count, retry handler, allowed methods, allowed status codes, maximum [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) time and allowed error codes. Delays between retries counts with function `1000 * Math.pow(2, retry) + Math.random() * 100`, where `retry` is attempt number (starts from 1). The `calculateDelay` property is a `function` that receives an object with `attemptCount`, `retryOptions`, `error` and `computedValue` properties for current retry count, the retry options, error and default computed value. The function must return a delay in milliseconds (or a Promise resolving with it) (`0` return value cancels retry). By default, it retries *only* on the specified methods, status codes, and on these network errors: - `ETIMEDOUT`: One of the [timeout](#timeout) limits were reached. - `ECONNRESET`: Connection was forcibly closed by a peer. - `EADDRINUSE`: Could not bind to any free port. - `ECONNREFUSED`: Connection was refused by the server. - `EPIPE`: The remote side of the stream being written has been closed. - `ENOTFOUND`: Couldn't resolve the hostname to an IP address. - `ENETUNREACH`: No internet connection. - `EAI_AGAIN`: DNS lookup timed out. __Note__: If `maxRetryAfter` is set to `undefined`, it will use `options.timeout`. __Note__: If [`Retry-After`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After) header is greater than `maxRetryAfter`, it will cancel the request. */ retry?: Partial | number; // From `http.RequestOptions` /** The IP address used to send the request from. */ localAddress?: string; socketPath?: string; /** The HTTP method used to make the request. @default 'GET' */ method?: Method; createConnection?: (options: http.RequestOptions, oncreate: (error: Error, socket: Socket) => void) => Socket; // From `http-cache-semantics` cacheOptions?: CacheOptions; // TODO: remove when Got 12 gets released /** If set to `false`, all invalid SSL certificates will be ignored and no error will be thrown. If set to `true`, it will throw an error whenever an invalid SSL certificate is detected. We strongly recommend to have this set to `true` for security reasons. @default true @example ``` const got = require('got'); (async () => { // Correct: await got('https://example.com', {rejectUnauthorized: true}); // You can disable it when developing an HTTPS app: await got('https://localhost', {rejectUnauthorized: false}); // Never do this: await got('https://example.com', {rejectUnauthorized: false}); })(); ``` */ rejectUnauthorized?: boolean; // Here for backwards compatibility /** Options for the advanced HTTPS API. */ https?: HTTPSOptions; } export interface Options extends PromiseOnly.Options, PlainOptions {} export interface HTTPSOptions { // From `http.RequestOptions` and `tls.CommonConnectionOptions` rejectUnauthorized?: https.RequestOptions['rejectUnauthorized']; // From `tls.ConnectionOptions` checkServerIdentity?: CheckServerIdentityFunction; // From `tls.SecureContextOptions` /** Override the default Certificate Authorities ([from Mozilla](https://ccadb-public.secure.force.com/mozilla/IncludedCACertificateReport)). @example ``` // Single Certificate Authority got('https://example.com', { https: { certificateAuthority: fs.readFileSync('./my_ca.pem') } }); ``` */ certificateAuthority?: SecureContextOptions['ca']; /** Private keys in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format. [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) allows the option of private keys being encrypted. Encrypted keys will be decrypted with `options.https.passphrase`. Multiple keys with different passphrases can be provided as an array of `{pem: , passphrase: }` */ key?: SecureContextOptions['key']; /** [Certificate chains](https://en.wikipedia.org/wiki/X.509#Certificate_chains_and_cross-certification) in [PEM](https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail) format. One cert chain should be provided per private key (`options.https.key`). When providing multiple cert chains, they do not have to be in the same order as their private keys in `options.https.key`. If the intermediate certificates are not provided, the peer will not be able to validate the certificate, and the handshake will fail. */ certificate?: SecureContextOptions['cert']; /** The passphrase to decrypt the `options.https.key` (if different keys have different passphrases refer to `options.https.key` documentation). */ passphrase?: SecureContextOptions['passphrase']; pfx?: SecureContextOptions['pfx']; } interface NormalizedPlainOptions extends PlainOptions { method: Method; url: URL; timeout: Delays; prefixUrl: string; ignoreInvalidCookies: boolean; decompress: boolean; searchParams?: URLSearchParams; cookieJar?: PromiseCookieJar; headers: Headers; context: Record; hooks: Required; followRedirect: boolean; maxRedirects: number; cache?: string | CacheableRequest.StorageAdapter; throwHttpErrors: boolean; dnsCache?: CacheableLookup; http2: boolean; allowGetBody: boolean; rejectUnauthorized: boolean; lookup?: CacheableLookup['lookup']; methodRewriting: boolean; username: string; password: string; parseJson: ParseJsonFunction; stringifyJson: StringifyJsonFunction; retry: RequiredRetryOptions; cacheOptions: CacheOptions; [kRequest]: HttpRequestFunction; [kIsNormalizedAlready]?: boolean; } export interface NormalizedOptions extends PromiseOnly.NormalizedOptions, NormalizedPlainOptions {} interface PlainDefaults { timeout: Delays; prefixUrl: string; method: Method; ignoreInvalidCookies: boolean; decompress: boolean; context: Record; cookieJar?: PromiseCookieJar | ToughCookieJar; dnsCache?: CacheableLookup; headers: Headers; hooks: Required; followRedirect: boolean; maxRedirects: number; cache?: string | CacheableRequest.StorageAdapter; throwHttpErrors: boolean; http2: boolean; allowGetBody: boolean; https?: HTTPSOptions; methodRewriting: boolean; parseJson: ParseJsonFunction; stringifyJson: StringifyJsonFunction; retry: RequiredRetryOptions; // Optional agent?: Agents | false; request?: RequestFunction; searchParams?: URLSearchParams; lookup?: CacheableLookup['lookup']; localAddress?: string; createConnection?: Options['createConnection']; // From `http-cache-semantics` cacheOptions: CacheOptions; } export interface Defaults extends PromiseOnly.Defaults, PlainDefaults {} export interface Progress { percent: number; transferred: number; total?: number; } export interface PlainResponse extends IncomingMessageWithTimings { /** The original request URL. */ requestUrl: string; /** The redirect URLs. */ redirectUrls: string[]; /** - `options` - The Got options that were set on this request. __Note__: This is not a [http.ClientRequest](https://nodejs.org/api/http.html#http_class_http_clientrequest). */ request: Request; /** The remote IP address. This is hopefully a temporary limitation, see [lukechilds/cacheable-request#86](https://github.com/lukechilds/cacheable-request/issues/86). __Note__: Not available when the response is cached. */ ip?: string; /** Whether the response was retrieved from the cache. */ isFromCache: boolean; /** The status code of the response. */ statusCode: number; /** The request URL or the final URL after redirects. */ url: string; /** The object contains the following properties: - `start` - Time when the request started. - `socket` - Time when a socket was assigned to the request. - `lookup` - Time when the DNS lookup finished. - `connect` - Time when the socket successfully connected. - `secureConnect` - Time when the socket securely connected. - `upload` - Time when the request finished uploading. - `response` - Time when the request fired `response` event. - `end` - Time when the response fired `end` event. - `error` - Time when the request fired `error` event. - `abort` - Time when the request fired `abort` event. - `phases` - `wait` - `timings.socket - timings.start` - `dns` - `timings.lookup - timings.socket` - `tcp` - `timings.connect - timings.lookup` - `tls` - `timings.secureConnect - timings.connect` - `request` - `timings.upload - (timings.secureConnect || timings.connect)` - `firstByte` - `timings.response - timings.upload` - `download` - `timings.end - timings.response` - `total` - `(timings.end || timings.error || timings.abort) - timings.start` If something has not been measured yet, it will be `undefined`. __Note__: The time is a `number` representing the milliseconds elapsed since the UNIX epoch. */ timings: Timings; /** The number of times the request was retried. */ retryCount: number; // Defined only if request errored /** The raw result of the request. */ rawBody?: Buffer; /** The result of the request. */ body?: unknown; } // For Promise support export interface Response extends PlainResponse { /** The result of the request. */ body: T; /** The raw result of the request. */ rawBody: Buffer; } export interface RequestEvents { /** `request` event to get the request object of the request. __Tip__: You can use `request` event to abort requests. @example ``` got.stream('https://github.com') .on('request', request => setTimeout(() => request.destroy(), 50)); ``` */ on: ((name: 'request', listener: (request: http.ClientRequest) => void) => T) /** The `response` event to get the response object of the final request. */ & ((name: 'response', listener: (response: R) => void) => T) /** The `redirect` event to get the response object of a redirect. The second argument is options for the next request to the redirect location. */ & ((name: 'redirect', listener: (response: R, nextOptions: N) => void) => T) /** Progress events for uploading (sending a request) and downloading (receiving a response). The `progress` argument is an object like: ```js { percent: 0.1, transferred: 1024, total: 10240 } ``` If the `content-length` header is missing, `total` will be `undefined`. @example ```js (async () => { const response = await got('https://sindresorhus.com') .on('downloadProgress', progress => { // Report download progress }) .on('uploadProgress', progress => { // Report upload progress }); console.log(response); })(); ``` */ & ((name: 'uploadProgress' | 'downloadProgress', listener: (progress: Progress) => void) => T) /** To enable retrying on a Got stream, it is required to have a `retry` handler attached. When this event is emitted, you should reset the stream you were writing to and prepare the body again. See `got.options.retry` for more information. */ & ((name: 'retry', listener: (retryCount: number, error: RequestError) => void) => T); } function validateSearchParameters(searchParameters: Record): asserts searchParameters is Record { // eslint-disable-next-line guard-for-in for (const key in searchParameters) { const value = searchParameters[key]; if (!is.string(value) && !is.number(value) && !is.boolean(value) && !is.null_(value) && !is.undefined(value)) { throw new TypeError(`The \`searchParams\` value '${String(value)}' must be a string, number, boolean or null`); } } } function isClientRequest(clientRequest: unknown): clientRequest is ClientRequest { return is.object(clientRequest) && !('statusCode' in clientRequest); } const cacheableStore = new WeakableMap(); const waitForOpenFile = async (file: ReadStream): Promise => new Promise((resolve, reject) => { const onError = (error: Error): void => { reject(error); }; // Node.js 12 has incomplete types if (!(file as any).pending) { resolve(); } file.once('error', onError); file.once('ready', () => { file.off('error', onError); resolve(); }); }); const redirectCodes: ReadonlySet = new Set([300, 301, 302, 303, 304, 307, 308]); type NonEnumerableProperty = 'context' | 'body' | 'json' | 'form'; const nonEnumerableProperties: NonEnumerableProperty[] = [ 'context', 'body', 'json', 'form' ]; export const setNonEnumerableProperties = (sources: Array, to: Options): void => { // Non enumerable properties shall not be merged const properties: Partial<{[Key in NonEnumerableProperty]: any}> = {}; for (const source of sources) { if (!source) { continue; } for (const name of nonEnumerableProperties) { if (!(name in source)) { continue; } properties[name] = { writable: true, configurable: true, enumerable: false, // @ts-expect-error TS doesn't see the check above value: source[name] }; } } Object.defineProperties(to, properties); }; /** An error to be thrown when a request fails. Contains a `code` property with error class code, like `ECONNREFUSED`. */ export class RequestError extends Error { code: string; stack!: string; declare readonly options: NormalizedOptions; readonly response?: Response; readonly request?: Request; readonly timings?: Timings; constructor(message: string, error: Partial, self: Request | NormalizedOptions) { super(message); Error.captureStackTrace(this, this.constructor); this.name = 'RequestError'; this.code = error.code ?? 'ERR_GOT_REQUEST_ERROR'; if (self instanceof Request) { Object.defineProperty(this, 'request', { enumerable: false, value: self }); Object.defineProperty(this, 'response', { enumerable: false, value: self[kResponse] }); Object.defineProperty(this, 'options', { // This fails because of TS 3.7.2 useDefineForClassFields // Ref: https://github.com/microsoft/TypeScript/issues/34972 enumerable: false, value: self.options }); } else { Object.defineProperty(this, 'options', { // This fails because of TS 3.7.2 useDefineForClassFields // Ref: https://github.com/microsoft/TypeScript/issues/34972 enumerable: false, value: self }); } this.timings = this.request?.timings; // Recover the original stacktrace if (is.string(error.stack) && is.string(this.stack)) { const indexOfMessage = this.stack.indexOf(this.message) + this.message.length; const thisStackTrace = this.stack.slice(indexOfMessage).split('\n').reverse(); const errorStackTrace = error.stack.slice(error.stack.indexOf(error.message!) + error.message!.length).split('\n').reverse(); // Remove duplicated traces while (errorStackTrace.length !== 0 && errorStackTrace[0] === thisStackTrace[0]) { thisStackTrace.shift(); } this.stack = `${this.stack.slice(0, indexOfMessage)}${thisStackTrace.reverse().join('\n')}${errorStackTrace.reverse().join('\n')}`; } } } /** An error to be thrown when the server redirects you more than ten times. Includes a `response` property. */ export class MaxRedirectsError extends RequestError { declare readonly response: Response; declare readonly request: Request; declare readonly timings: Timings; constructor(request: Request) { super(`Redirected ${request.options.maxRedirects} times. Aborting.`, {}, request); this.name = 'MaxRedirectsError'; this.code = 'ERR_TOO_MANY_REDIRECTS'; } } /** An error to be thrown when the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304. Includes a `response` property. */ export class HTTPError extends RequestError { declare readonly response: Response; declare readonly request: Request; declare readonly timings: Timings; constructor(response: Response) { super(`Response code ${response.statusCode} (${response.statusMessage!})`, {}, response.request); this.name = 'HTTPError'; this.code = 'ERR_NON_2XX_3XX_RESPONSE'; } } /** An error to be thrown when a cache method fails. For example, if the database goes down or there's a filesystem error. */ export class CacheError extends RequestError { declare readonly request: Request; constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'CacheError'; this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_CACHE_ACCESS' : this.code; } } /** An error to be thrown when the request body is a stream and an error occurs while reading from that stream. */ export class UploadError extends RequestError { declare readonly request: Request; constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'UploadError'; this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_UPLOAD' : this.code; } } /** An error to be thrown when the request is aborted due to a timeout. Includes an `event` and `timings` property. */ export class TimeoutError extends RequestError { declare readonly request: Request; readonly timings: Timings; readonly event: string; constructor(error: TimedOutTimeoutError, timings: Timings, request: Request) { super(error.message, error, request); this.name = 'TimeoutError'; this.event = error.event; this.timings = timings; } } /** An error to be thrown when reading from response stream fails. */ export class ReadError extends RequestError { declare readonly request: Request; declare readonly response: Response; declare readonly timings: Timings; constructor(error: Error, request: Request) { super(error.message, error, request); this.name = 'ReadError'; this.code = this.code === 'ERR_GOT_REQUEST_ERROR' ? 'ERR_READING_RESPONSE_STREAM' : this.code; } } /** An error to be thrown when given an unsupported protocol. */ export class UnsupportedProtocolError extends RequestError { constructor(options: NormalizedOptions) { super(`Unsupported protocol "${options.url.protocol}"`, {}, options); this.name = 'UnsupportedProtocolError'; this.code = 'ERR_UNSUPPORTED_PROTOCOL'; } } const proxiedRequestEvents = [ 'socket', 'connect', 'continue', 'information', 'upgrade', 'timeout' ]; export default class Request extends Duplex implements RequestEvents { ['constructor']: typeof Request; declare [kUnproxyEvents]: () => void; declare _cannotHaveBody: boolean; [kDownloadedSize]: number; [kUploadedSize]: number; [kStopReading]: boolean; [kTriggerRead]: boolean; [kBody]: Options['body']; [kJobs]: Array<() => void>; [kRetryTimeout]?: NodeJS.Timeout; [kBodySize]?: number; [kServerResponsesPiped]: Set; [kIsFromCache]?: boolean; [kStartedReading]?: boolean; [kCancelTimeouts]?: () => void; [kResponseSize]?: number; [kResponse]?: IncomingMessageWithTimings; [kOriginalResponse]?: IncomingMessageWithTimings; [kRequest]?: ClientRequest; _noPipe?: boolean; _progressCallbacks: Array<() => void>; declare options: NormalizedOptions; declare requestUrl: string; requestInitialized: boolean; redirects: string[]; retryCount: number; constructor(url: string | URL | undefined, options: Options = {}, defaults?: Defaults) { super({ // This must be false, to enable throwing after destroy // It is used for retry logic in Promise API autoDestroy: false, // It needs to be zero because we're just proxying the data to another stream highWaterMark: 0 }); this[kDownloadedSize] = 0; this[kUploadedSize] = 0; this.requestInitialized = false; this[kServerResponsesPiped] = new Set(); this.redirects = []; this[kStopReading] = false; this[kTriggerRead] = false; this[kJobs] = []; this.retryCount = 0; // TODO: Remove this when targeting Node.js >= 12 this._progressCallbacks = []; const unlockWrite = (): void => this._unlockWrite(); const lockWrite = (): void => this._lockWrite(); this.on('pipe', (source: Writable) => { source.prependListener('data', unlockWrite); source.on('data', lockWrite); source.prependListener('end', unlockWrite); source.on('end', lockWrite); }); this.on('unpipe', (source: Writable) => { source.off('data', unlockWrite); source.off('data', lockWrite); source.off('end', unlockWrite); source.off('end', lockWrite); }); this.on('pipe', source => { if (source instanceof IncomingMessage) { this.options.headers = { ...source.headers, ...this.options.headers }; } }); const {json, body, form} = options; if (json || body || form) { this._lockWrite(); } if (kIsNormalizedAlready in options) { this.options = options as NormalizedOptions; } else { try { // @ts-expect-error Common TypeScript bug saying that `this.constructor` is not accessible this.options = this.constructor.normalizeArguments(url, options, defaults); } catch (error) { // TODO: Move this to `_destroy()` if (is.nodeStream(options.body)) { options.body.destroy(); } this.destroy(error); return; } } (async () => { try { if (this.options.body instanceof ReadStream) { await waitForOpenFile(this.options.body); } const {url: normalizedURL} = this.options; if (!normalizedURL) { throw new TypeError('Missing `url` property'); } this.requestUrl = normalizedURL.toString(); decodeURI(this.requestUrl); await this._finalizeBody(); await this._makeRequest(); if (this.destroyed) { this[kRequest]?.destroy(); return; } // Queued writes etc. for (const job of this[kJobs]) { job(); } // Prevent memory leak this[kJobs].length = 0; this.requestInitialized = true; } catch (error) { if (error instanceof RequestError) { this._beforeError(error); return; } // This is a workaround for https://github.com/nodejs/node/issues/33335 if (!this.destroyed) { this.destroy(error); } } })(); } static normalizeArguments(url?: string | URL, options?: Options, defaults?: Defaults): NormalizedOptions { const rawOptions = options; if (is.object(url) && !is.urlInstance(url)) { options = {...defaults, ...(url as Options), ...options}; } else { if (url && options && options.url !== undefined) { throw new TypeError('The `url` option is mutually exclusive with the `input` argument'); } options = {...defaults, ...options}; if (url !== undefined) { options.url = url; } if (is.urlInstance(options.url)) { options.url = new URL(options.url.toString()); } } // TODO: Deprecate URL options in Got 12. // Support extend-specific options if (options.cache === false) { options.cache = undefined; } if (options.dnsCache === false) { options.dnsCache = undefined; } // Nice type assertions assert.any([is.string, is.undefined], options.method); assert.any([is.object, is.undefined], options.headers); assert.any([is.string, is.urlInstance, is.undefined], options.prefixUrl); assert.any([is.object, is.undefined], options.cookieJar); assert.any([is.object, is.string, is.undefined], options.searchParams); assert.any([is.object, is.string, is.undefined], options.cache); assert.any([is.object, is.number, is.undefined], options.timeout); assert.any([is.object, is.undefined], options.context); assert.any([is.object, is.undefined], options.hooks); assert.any([is.boolean, is.undefined], options.decompress); assert.any([is.boolean, is.undefined], options.ignoreInvalidCookies); assert.any([is.boolean, is.undefined], options.followRedirect); assert.any([is.number, is.undefined], options.maxRedirects); assert.any([is.boolean, is.undefined], options.throwHttpErrors); assert.any([is.boolean, is.undefined], options.http2); assert.any([is.boolean, is.undefined], options.allowGetBody); assert.any([is.string, is.undefined], options.localAddress); assert.any([isDnsLookupIpVersion, is.undefined], options.dnsLookupIpVersion); assert.any([is.object, is.undefined], options.https); assert.any([is.boolean, is.undefined], options.rejectUnauthorized); if (options.https) { assert.any([is.boolean, is.undefined], options.https.rejectUnauthorized); assert.any([is.function_, is.undefined], options.https.checkServerIdentity); assert.any([is.string, is.object, is.array, is.undefined], options.https.certificateAuthority); assert.any([is.string, is.object, is.array, is.undefined], options.https.key); assert.any([is.string, is.object, is.array, is.undefined], options.https.certificate); assert.any([is.string, is.undefined], options.https.passphrase); assert.any([is.string, is.buffer, is.array, is.undefined], options.https.pfx); } assert.any([is.object, is.undefined], options.cacheOptions); // `options.method` if (is.string(options.method)) { options.method = options.method.toUpperCase() as Method; } else { options.method = 'GET'; } // `options.headers` if (options.headers === defaults?.headers) { options.headers = {...options.headers}; } else { options.headers = lowercaseKeys({...(defaults?.headers), ...options.headers}); } // Disallow legacy `url.Url` if ('slashes' in options) { throw new TypeError('The legacy `url.Url` has been deprecated. Use `URL` instead.'); } // `options.auth` if ('auth' in options) { throw new TypeError('Parameter `auth` is deprecated. Use `username` / `password` instead.'); } // `options.searchParams` if ('searchParams' in options) { if (options.searchParams && options.searchParams !== defaults?.searchParams) { let searchParameters: URLSearchParams; if (is.string(options.searchParams) || (options.searchParams instanceof URLSearchParams)) { searchParameters = new URLSearchParams(options.searchParams); } else { validateSearchParameters(options.searchParams); searchParameters = new URLSearchParams(); // eslint-disable-next-line guard-for-in for (const key in options.searchParams) { const value = options.searchParams[key]; if (value === null) { searchParameters.append(key, ''); } else if (value !== undefined) { searchParameters.append(key, value as string); } } } // `normalizeArguments()` is also used to merge options defaults?.searchParams?.forEach((value, key) => { // Only use default if one isn't already defined if (!searchParameters.has(key)) { searchParameters.append(key, value); } }); options.searchParams = searchParameters; } } // `options.username` & `options.password` options.username = options.username ?? ''; options.password = options.password ?? ''; // `options.prefixUrl` & `options.url` if (is.undefined(options.prefixUrl)) { options.prefixUrl = defaults?.prefixUrl ?? ''; } else { options.prefixUrl = options.prefixUrl.toString(); if (options.prefixUrl !== '' && !options.prefixUrl.endsWith('/')) { options.prefixUrl += '/'; } } if (is.string(options.url)) { if (options.url.startsWith('/')) { throw new Error('`input` must not start with a slash when using `prefixUrl`'); } options.url = optionsToUrl(options.prefixUrl + options.url, options as Options & {searchParams?: URLSearchParams}); } else if ((is.undefined(options.url) && options.prefixUrl !== '') || options.protocol) { options.url = optionsToUrl(options.prefixUrl, options as Options & {searchParams?: URLSearchParams}); } if (options.url) { if ('port' in options) { delete options.port; } // Make it possible to change `options.prefixUrl` let {prefixUrl} = options; Object.defineProperty(options, 'prefixUrl', { set: (value: string) => { const url = options!.url as URL; if (!url.href.startsWith(value)) { throw new Error(`Cannot change \`prefixUrl\` from ${prefixUrl} to ${value}: ${url.href}`); } options!.url = new URL(value + url.href.slice(prefixUrl.length)); prefixUrl = value; }, get: () => prefixUrl }); // Support UNIX sockets let {protocol} = options.url; if (protocol === 'unix:') { protocol = 'http:'; options.url = new URL(`http://unix${options.url.pathname}${options.url.search}`); } // Set search params if (options.searchParams) { // eslint-disable-next-line @typescript-eslint/no-base-to-string options.url.search = options.searchParams.toString(); } // Protocol check if (protocol !== 'http:' && protocol !== 'https:') { throw new UnsupportedProtocolError(options as NormalizedOptions); } // Update `username` if (options.username === '') { options.username = options.url.username; } else { options.url.username = options.username; } // Update `password` if (options.password === '') { options.password = options.url.password; } else { options.url.password = options.password; } } // `options.cookieJar` const {cookieJar} = options; if (cookieJar) { let {setCookie, getCookieString} = cookieJar; assert.function_(setCookie); assert.function_(getCookieString); /* istanbul ignore next: Horrible `tough-cookie` v3 check */ if (setCookie.length === 4 && getCookieString.length === 0) { setCookie = promisify(setCookie.bind(options.cookieJar)); getCookieString = promisify(getCookieString.bind(options.cookieJar)); options.cookieJar = { setCookie, getCookieString: getCookieString as PromiseCookieJar['getCookieString'] }; } } // `options.cache` const {cache} = options; if (cache) { if (!cacheableStore.has(cache)) { cacheableStore.set(cache, new CacheableRequest( ((requestOptions: RequestOptions, handler?: (response: IncomingMessageWithTimings) => void): ClientRequest => { const result = (requestOptions as Pick)[kRequest](requestOptions, handler); // TODO: remove this when `cacheable-request` supports async request functions. if (is.promise(result)) { // @ts-expect-error // We only need to implement the error handler in order to support HTTP2 caching. // The result will be a promise anyway. result.once = (event: string, handler: (reason: unknown) => void) => { if (event === 'error') { result.catch(handler); } else if (event === 'abort') { // The empty catch is needed here in case when // it rejects before it's `await`ed in `_makeRequest`. (async () => { try { const request = (await result) as ClientRequest; request.once('abort', handler); } catch {} })(); } else { /* istanbul ignore next: safety check */ throw new Error(`Unknown HTTP2 promise event: ${event}`); } return result; }; } return result; }) as HttpRequestFunction, cache as CacheableRequest.StorageAdapter )); } } // `options.cacheOptions` options.cacheOptions = {...options.cacheOptions}; // `options.dnsCache` if (options.dnsCache === true) { if (!globalDnsCache) { globalDnsCache = new CacheableLookup(); } options.dnsCache = globalDnsCache; } else if (!is.undefined(options.dnsCache) && !options.dnsCache.lookup) { throw new TypeError(`Parameter \`dnsCache\` must be a CacheableLookup instance or a boolean, got ${is(options.dnsCache)}`); } // `options.timeout` if (is.number(options.timeout)) { options.timeout = {request: options.timeout}; } else if (defaults && options.timeout !== defaults.timeout) { options.timeout = { ...defaults.timeout, ...options.timeout }; } else { options.timeout = {...options.timeout}; } // `options.context` if (!options.context) { options.context = {}; } // `options.hooks` const areHooksDefault = options.hooks === defaults?.hooks; options.hooks = {...options.hooks}; for (const event of knownHookEvents) { if (event in options.hooks) { if (is.array(options.hooks[event])) { // See https://github.com/microsoft/TypeScript/issues/31445#issuecomment-576929044 (options.hooks as any)[event] = [...options.hooks[event]!]; } else { throw new TypeError(`Parameter \`${event}\` must be an Array, got ${is(options.hooks[event])}`); } } else { options.hooks[event] = []; } } if (defaults && !areHooksDefault) { for (const event of knownHookEvents) { const defaultHooks = defaults.hooks[event]; if (defaultHooks.length > 0) { // See https://github.com/microsoft/TypeScript/issues/31445#issuecomment-576929044 (options.hooks as any)[event] = [ ...defaults.hooks[event], ...options.hooks[event]! ]; } } } // DNS options if ('family' in options) { deprecationWarning('"options.family" was never documented, please use "options.dnsLookupIpVersion"'); } // HTTPS options if (defaults?.https) { options.https = {...defaults.https, ...options.https}; } if ('rejectUnauthorized' in options) { deprecationWarning('"options.rejectUnauthorized" is now deprecated, please use "options.https.rejectUnauthorized"'); } if ('checkServerIdentity' in options) { deprecationWarning('"options.checkServerIdentity" was never documented, please use "options.https.checkServerIdentity"'); } if ('ca' in options) { deprecationWarning('"options.ca" was never documented, please use "options.https.certificateAuthority"'); } if ('key' in options) { deprecationWarning('"options.key" was never documented, please use "options.https.key"'); } if ('cert' in options) { deprecationWarning('"options.cert" was never documented, please use "options.https.certificate"'); } if ('passphrase' in options) { deprecationWarning('"options.passphrase" was never documented, please use "options.https.passphrase"'); } if ('pfx' in options) { deprecationWarning('"options.pfx" was never documented, please use "options.https.pfx"'); } // Other options if ('followRedirects' in options) { throw new TypeError('The `followRedirects` option does not exist. Use `followRedirect` instead.'); } if (options.agent) { for (const key in options.agent) { if (key !== 'http' && key !== 'https' && key !== 'http2') { throw new TypeError(`Expected the \`options.agent\` properties to be \`http\`, \`https\` or \`http2\`, got \`${key}\``); } } } options.maxRedirects = options.maxRedirects ?? 0; // Set non-enumerable properties setNonEnumerableProperties([defaults, rawOptions], options); return normalizePromiseArguments(options as NormalizedOptions, defaults); } _lockWrite(): void { const onLockedWrite = (): never => { throw new TypeError('The payload has been already provided'); }; this.write = onLockedWrite; this.end = onLockedWrite; } _unlockWrite(): void { this.write = super.write; this.end = super.end; } async _finalizeBody(): Promise { const {options} = this; const {headers} = options; const isForm = !is.undefined(options.form); const isJSON = !is.undefined(options.json); const isBody = !is.undefined(options.body); const hasPayload = isForm || isJSON || isBody; const cannotHaveBody = withoutBody.has(options.method) && !(options.method === 'GET' && options.allowGetBody); this._cannotHaveBody = cannotHaveBody; if (hasPayload) { if (cannotHaveBody) { throw new TypeError(`The \`${options.method}\` method cannot be used with a body`); } if ([isBody, isForm, isJSON].filter(isTrue => isTrue).length > 1) { throw new TypeError('The `body`, `json` and `form` options are mutually exclusive'); } if ( isBody && !(options.body instanceof Readable) && !is.string(options.body) && !is.buffer(options.body) && !isFormData(options.body) ) { throw new TypeError('The `body` option must be a stream.Readable, string or Buffer'); } if (isForm && !is.object(options.form)) { throw new TypeError('The `form` option must be an Object'); } { // Serialize body const noContentType = !is.string(headers['content-type']); if (isBody) { // Special case for https://github.com/form-data/form-data if (isFormData(options.body) && noContentType) { headers['content-type'] = `multipart/form-data; boundary=${options.body.getBoundary()}`; } this[kBody] = options.body; } else if (isForm) { if (noContentType) { headers['content-type'] = 'application/x-www-form-urlencoded'; } this[kBody] = (new URLSearchParams(options.form as Record)).toString(); } else { if (noContentType) { headers['content-type'] = 'application/json'; } this[kBody] = options.stringifyJson(options.json); } const uploadBodySize = await getBodySize(this[kBody], options.headers); // See https://tools.ietf.org/html/rfc7230#section-3.3.2 // A user agent SHOULD send a Content-Length in a request message when // no Transfer-Encoding is sent and the request method defines a meaning // for an enclosed payload body. For example, a Content-Length header // field is normally sent in a POST request even when the value is 0 // (indicating an empty payload body). A user agent SHOULD NOT send a // Content-Length header field when the request message does not contain // a payload body and the method semantics do not anticipate such a // body. if (is.undefined(headers['content-length']) && is.undefined(headers['transfer-encoding'])) { if (!cannotHaveBody && !is.undefined(uploadBodySize)) { headers['content-length'] = String(uploadBodySize); } } } } else if (cannotHaveBody) { this._lockWrite(); } else { this._unlockWrite(); } this[kBodySize] = Number(headers['content-length']) || undefined; } async _onResponseBase(response: IncomingMessageWithTimings): Promise { const {options} = this; const {url} = options; this[kOriginalResponse] = response; if (options.decompress) { response = decompressResponse(response); } const statusCode = response.statusCode!; const typedResponse = response as Response; typedResponse.statusMessage = typedResponse.statusMessage ? typedResponse.statusMessage : http.STATUS_CODES[statusCode]; typedResponse.url = options.url.toString(); typedResponse.requestUrl = this.requestUrl; typedResponse.redirectUrls = this.redirects; typedResponse.request = this; typedResponse.isFromCache = (response as any).fromCache || false; typedResponse.ip = this.ip; typedResponse.retryCount = this.retryCount; this[kIsFromCache] = typedResponse.isFromCache; this[kResponseSize] = Number(response.headers['content-length']) || undefined; this[kResponse] = response; response.once('end', () => { this[kResponseSize] = this[kDownloadedSize]; this.emit('downloadProgress', this.downloadProgress); }); response.once('error', (error: Error) => { // Force clean-up, because some packages don't do this. // TODO: Fix decompress-response response.destroy(); this._beforeError(new ReadError(error, this)); }); response.once('aborted', () => { this._beforeError(new ReadError({ name: 'Error', message: 'The server aborted pending request', code: 'ECONNRESET' }, this)); }); this.emit('downloadProgress', this.downloadProgress); const rawCookies = response.headers['set-cookie']; if (is.object(options.cookieJar) && rawCookies) { let promises: Array> = rawCookies.map(async (rawCookie: string) => (options.cookieJar as PromiseCookieJar).setCookie(rawCookie, url.toString())); if (options.ignoreInvalidCookies) { promises = promises.map(async p => p.catch(() => {})); } try { await Promise.all(promises); } catch (error) { this._beforeError(error); return; } } if (options.followRedirect && response.headers.location && redirectCodes.has(statusCode)) { // We're being redirected, we don't care about the response. // It'd be best to abort the request, but we can't because // we would have to sacrifice the TCP connection. We don't want that. response.resume(); if (this[kRequest]) { this[kCancelTimeouts]!(); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete this[kRequest]; this[kUnproxyEvents](); } const shouldBeGet = statusCode === 303 && options.method !== 'GET' && options.method !== 'HEAD'; if (shouldBeGet || !options.methodRewriting) { // 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 ('body' in options) { delete options.body; } if ('json' in options) { delete options.json; } if ('form' in options) { delete options.form; } this[kBody] = undefined; delete options.headers['content-length']; } if (this.redirects.length >= options.maxRedirects) { this._beforeError(new MaxRedirectsError(this)); return; } try { // Do not remove. See https://github.com/sindresorhus/got/pull/214 const redirectBuffer = Buffer.from(response.headers.location, 'binary').toString(); // Handles invalid URLs. See https://github.com/sindresorhus/got/issues/604 const redirectUrl = new URL(redirectBuffer, url); const redirectString = redirectUrl.toString(); decodeURI(redirectString); // eslint-disable-next-line no-inner-declarations function isUnixSocketURL(url: URL) { return url.protocol === 'unix:' || url.hostname === 'unix'; } if (!isUnixSocketURL(url) && isUnixSocketURL(redirectUrl)) { this._beforeError(new RequestError('Cannot redirect to UNIX socket', {}, this)); return; } // Redirecting to a different site, clear sensitive data. if (redirectUrl.hostname !== url.hostname || redirectUrl.port !== url.port) { if ('host' in options.headers) { delete options.headers.host; } if ('cookie' in options.headers) { delete options.headers.cookie; } if ('authorization' in options.headers) { delete options.headers.authorization; } if (options.username || options.password) { options.username = ''; options.password = ''; } } else { redirectUrl.username = options.username; redirectUrl.password = options.password; } this.redirects.push(redirectString); options.url = redirectUrl; for (const hook of options.hooks.beforeRedirect) { // eslint-disable-next-line no-await-in-loop await hook(options, typedResponse); } this.emit('redirect', typedResponse, options); await this._makeRequest(); } catch (error) { this._beforeError(error); return; } return; } if (options.isStream && options.throwHttpErrors && !isResponseOk(typedResponse)) { this._beforeError(new HTTPError(typedResponse)); return; } response.on('readable', () => { if (this[kTriggerRead]) { this._read(); } }); this.on('resume', () => { response.resume(); }); this.on('pause', () => { response.pause(); }); response.once('end', () => { this.push(null); }); this.emit('response', response); for (const destination of this[kServerResponsesPiped]) { if (destination.headersSent) { continue; } // eslint-disable-next-line guard-for-in for (const key in response.headers) { const isAllowed = options.decompress ? key !== 'content-encoding' : true; const value = response.headers[key]; if (isAllowed) { destination.setHeader(key, value!); } } destination.statusCode = statusCode; } } async _onResponse(response: IncomingMessageWithTimings): Promise { try { await this._onResponseBase(response); } catch (error) { /* istanbul ignore next: better safe than sorry */ this._beforeError(error); } } _onRequest(request: ClientRequest): void { const {options} = this; const {timeout, url} = options; timer(request); this[kCancelTimeouts] = timedOut(request, timeout, url); const responseEventName = options.cache ? 'cacheableResponse' : 'response'; request.once(responseEventName, (response: IncomingMessageWithTimings) => { void this._onResponse(response); }); request.once('error', (error: Error) => { // Force clean-up, because some packages (e.g. nock) don't do this. request.destroy(); // Node.js <= 12.18.2 mistakenly emits the response `end` first. (request as ClientRequest & {res: IncomingMessage | undefined}).res?.removeAllListeners('end'); error = error instanceof TimedOutTimeoutError ? new TimeoutError(error, this.timings!, this) : new RequestError(error.message, error, this); this._beforeError(error as RequestError); }); this[kUnproxyEvents] = proxyEvents(request, this, proxiedRequestEvents); this[kRequest] = request; this.emit('uploadProgress', this.uploadProgress); // Send body const body = this[kBody]; const currentRequest = this.redirects.length === 0 ? this : request; if (is.nodeStream(body)) { body.pipe(currentRequest); body.once('error', (error: NodeJS.ErrnoException) => { this._beforeError(new UploadError(error, this)); }); } else { this._unlockWrite(); if (!is.undefined(body)) { this._writeRequest(body, undefined, () => {}); currentRequest.end(); this._lockWrite(); } else if (this._cannotHaveBody || this._noPipe) { currentRequest.end(); this._lockWrite(); } } this.emit('request', request); } async _createCacheableRequest(url: URL, options: RequestOptions): Promise { return new Promise((resolve, reject) => { // TODO: Remove `utils/url-to-options.ts` when `cacheable-request` is fixed Object.assign(options, urlToOptions(url)); // `http-cache-semantics` checks this // TODO: Fix this ignore. // @ts-expect-error delete (options as unknown as NormalizedOptions).url; let request: ClientRequest | Promise; // This is ugly const cacheRequest = cacheableStore.get((options as any).cache)!(options, async response => { // TODO: Fix `cacheable-response` (response as any)._readableState.autoDestroy = false; if (request) { (await request).emit('cacheableResponse', response); } resolve(response as unknown as ResponseLike); }); // Restore options (options as unknown as NormalizedOptions).url = url; cacheRequest.once('error', reject); cacheRequest.once('request', async (requestOrPromise: ClientRequest | Promise) => { request = requestOrPromise; resolve(request); }); }); } async _makeRequest(): Promise { const {options} = this; const {headers} = options; for (const key in headers) { if (is.undefined(headers[key])) { // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete headers[key]; } else if (is.null_(headers[key])) { throw new TypeError(`Use \`undefined\` instead of \`null\` to delete the \`${key}\` header`); } } if (options.decompress && is.undefined(headers['accept-encoding'])) { headers['accept-encoding'] = supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate'; } // Set cookies if (options.cookieJar) { const cookieString: string = await options.cookieJar.getCookieString(options.url.toString()); if (is.nonEmptyString(cookieString)) { options.headers.cookie = cookieString; } } for (const hook of options.hooks.beforeRequest) { // eslint-disable-next-line no-await-in-loop const result = await hook(options); if (!is.undefined(result)) { // @ts-expect-error Skip the type mismatch to support abstract responses options.request = () => result; break; } } if (options.body && this[kBody] !== options.body) { this[kBody] = options.body; } const {agent, request, timeout, url} = options; if (options.dnsCache && !('lookup' in options)) { options.lookup = options.dnsCache.lookup; } // UNIX sockets if (url.hostname === 'unix') { const matches = /(?.+?):(?.+)/.exec(`${url.pathname}${url.search}`); if (matches?.groups) { const {socketPath, path} = matches.groups; Object.assign(options, { socketPath, path, host: '' }); } } const isHttps = url.protocol === 'https:'; // Fallback function let fallbackFn: HttpRequestFunction; if (options.http2) { fallbackFn = http2wrapper.auto; } else { fallbackFn = isHttps ? https.request : http.request; } const realFn = options.request ?? fallbackFn; // Cache support const fn = options.cache ? this._createCacheableRequest : realFn; // Pass an agent directly when HTTP2 is disabled if (agent && !options.http2) { (options as unknown as RequestOptions).agent = agent[isHttps ? 'https' : 'http']; } // Prepare plain HTTP request options options[kRequest] = realFn as HttpRequestFunction; delete options.request; // TODO: Fix this ignore. // @ts-expect-error delete options.timeout; const requestOptions = options as unknown as (RealRequestOptions & CacheOptions); requestOptions.shared = options.cacheOptions?.shared; requestOptions.cacheHeuristic = options.cacheOptions?.cacheHeuristic; requestOptions.immutableMinTimeToLive = options.cacheOptions?.immutableMinTimeToLive; requestOptions.ignoreCargoCult = options.cacheOptions?.ignoreCargoCult; // If `dnsLookupIpVersion` is not present do not override `family` if (options.dnsLookupIpVersion !== undefined) { try { requestOptions.family = dnsLookupIpVersionToFamily(options.dnsLookupIpVersion); } catch { throw new Error('Invalid `dnsLookupIpVersion` option value'); } } // HTTPS options remapping if (options.https) { if ('rejectUnauthorized' in options.https) { requestOptions.rejectUnauthorized = options.https.rejectUnauthorized; } if (options.https.checkServerIdentity) { requestOptions.checkServerIdentity = options.https.checkServerIdentity; } if (options.https.certificateAuthority) { requestOptions.ca = options.https.certificateAuthority; } if (options.https.certificate) { requestOptions.cert = options.https.certificate; } if (options.https.key) { requestOptions.key = options.https.key; } if (options.https.passphrase) { requestOptions.passphrase = options.https.passphrase; } if (options.https.pfx) { requestOptions.pfx = options.https.pfx; } } try { let requestOrResponse = await fn(url, requestOptions); if (is.undefined(requestOrResponse)) { requestOrResponse = fallbackFn(url, requestOptions); } // Restore options options.request = request; options.timeout = timeout; options.agent = agent; // HTTPS options restore if (options.https) { if ('rejectUnauthorized' in options.https) { delete requestOptions.rejectUnauthorized; } if (options.https.checkServerIdentity) { // @ts-expect-error - This one will be removed when we remove the alias. delete requestOptions.checkServerIdentity; } if (options.https.certificateAuthority) { delete requestOptions.ca; } if (options.https.certificate) { delete requestOptions.cert; } if (options.https.key) { delete requestOptions.key; } if (options.https.passphrase) { delete requestOptions.passphrase; } if (options.https.pfx) { delete requestOptions.pfx; } } if (isClientRequest(requestOrResponse)) { this._onRequest(requestOrResponse); // Emit the response after the stream has been ended } else if (this.writable) { this.once('finish', () => { void this._onResponse(requestOrResponse as IncomingMessageWithTimings); }); this._unlockWrite(); this.end(); this._lockWrite(); } else { void this._onResponse(requestOrResponse as IncomingMessageWithTimings); } } catch (error) { if (error instanceof CacheableRequest.CacheError) { throw new CacheError(error, this); } throw new RequestError(error.message, error, this); } } async _error(error: RequestError): Promise { try { for (const hook of this.options.hooks.beforeError) { // eslint-disable-next-line no-await-in-loop error = await hook(error); } } catch (error_) { error = new RequestError(error_.message, error_, this); } this.destroy(error); } _beforeError(error: Error): void { if (this[kStopReading]) { return; } const {options} = this; const retryCount = this.retryCount + 1; this[kStopReading] = true; if (!(error instanceof RequestError)) { error = new RequestError(error.message, error, this); } const typedError = error as RequestError; const {response} = typedError; void (async () => { if (response && !response.body) { response.setEncoding((this as any)._readableState.encoding); try { response.rawBody = await getBuffer(response); response.body = response.rawBody.toString(); } catch {} } if (this.listenerCount('retry') !== 0) { let backoff: number; try { let retryAfter; if (response && 'retry-after' in response.headers) { retryAfter = Number(response.headers['retry-after']); if (Number.isNaN(retryAfter)) { retryAfter = Date.parse(response.headers['retry-after']!) - Date.now(); if (retryAfter <= 0) { retryAfter = 1; } } else { retryAfter *= 1000; } } backoff = await options.retry.calculateDelay({ attemptCount: retryCount, retryOptions: options.retry, error: typedError, retryAfter, computedValue: calculateRetryDelay({ attemptCount: retryCount, retryOptions: options.retry, error: typedError, retryAfter, computedValue: 0 }) }); } catch (error_) { void this._error(new RequestError(error_.message, error_, this)); return; } if (backoff) { const retry = async (): Promise => { try { for (const hook of this.options.hooks.beforeRetry) { // eslint-disable-next-line no-await-in-loop await hook(this.options, typedError, retryCount); } } catch (error_) { void this._error(new RequestError(error_.message, error, this)); return; } // Something forced us to abort the retry if (this.destroyed) { return; } this.destroy(); this.emit('retry', retryCount, error); }; this[kRetryTimeout] = setTimeout(retry, backoff); return; } } void this._error(typedError); })(); } _read(): void { this[kTriggerRead] = true; const response = this[kResponse]; if (response && !this[kStopReading]) { // We cannot put this in the `if` above // because `.read()` also triggers the `end` event if (response.readableLength) { this[kTriggerRead] = false; } let data; while ((data = response.read()) !== null) { this[kDownloadedSize] += data.length; this[kStartedReading] = true; const progress = this.downloadProgress; if (progress.percent < 1) { this.emit('downloadProgress', progress); } this.push(data); } } } // Node.js 12 has incorrect types, so the encoding must be a string _write(chunk: any, encoding: string | undefined, callback: (error?: Error | null) => void): void { const write = (): void => { this._writeRequest(chunk, encoding as BufferEncoding, callback); }; if (this.requestInitialized) { write(); } else { this[kJobs].push(write); } } _writeRequest(chunk: any, encoding: BufferEncoding | undefined, callback: (error?: Error | null) => void): void { if (this[kRequest]!.destroyed) { // Probably the `ClientRequest` instance will throw return; } this._progressCallbacks.push((): void => { this[kUploadedSize] += Buffer.byteLength(chunk, encoding); const progress = this.uploadProgress; if (progress.percent < 1) { this.emit('uploadProgress', progress); } }); // TODO: What happens if it's from cache? Then this[kRequest] won't be defined. this[kRequest]!.write(chunk, encoding!, (error?: Error | null) => { if (!error && this._progressCallbacks.length > 0) { this._progressCallbacks.shift()!(); } callback(error); }); } _final(callback: (error?: Error | null) => void): void { const endRequest = (): void => { // FIX: Node.js 10 calls the write callback AFTER the end callback! while (this._progressCallbacks.length !== 0) { this._progressCallbacks.shift()!(); } // We need to check if `this[kRequest]` is present, // because it isn't when we use cache. if (!(kRequest in this)) { callback(); return; } if (this[kRequest]!.destroyed) { callback(); return; } this[kRequest]!.end((error?: Error | null) => { if (!error) { this[kBodySize] = this[kUploadedSize]; this.emit('uploadProgress', this.uploadProgress); this[kRequest]!.emit('upload-complete'); } callback(error); }); }; if (this.requestInitialized) { endRequest(); } else { this[kJobs].push(endRequest); } } _destroy(error: Error | null, callback: (error: Error | null) => void): void { this[kStopReading] = true; // Prevent further retries clearTimeout(this[kRetryTimeout] as NodeJS.Timeout); if (kRequest in this) { this[kCancelTimeouts]!(); // TODO: Remove the next `if` when these get fixed: // - https://github.com/nodejs/node/issues/32851 if (!this[kResponse]?.complete) { this[kRequest]!.destroy(); } } if (error !== null && !is.undefined(error) && !(error instanceof RequestError)) { error = new RequestError(error.message, error, this); } callback(error); } get _isAboutToError() { return this[kStopReading]; } /** The remote IP address. */ get ip(): string | undefined { return this.socket?.remoteAddress; } /** Indicates whether the request has been aborted or not. */ get aborted(): boolean { return (this[kRequest]?.destroyed ?? this.destroyed) && !(this[kOriginalResponse]?.complete); } get socket(): Socket | undefined { return this[kRequest]?.socket ?? undefined; } /** Progress event for downloading (receiving a response). */ get downloadProgress(): Progress { let percent; if (this[kResponseSize]) { percent = this[kDownloadedSize] / this[kResponseSize]!; } else if (this[kResponseSize] === this[kDownloadedSize]) { percent = 1; } else { percent = 0; } return { percent, transferred: this[kDownloadedSize], total: this[kResponseSize] }; } /** Progress event for uploading (sending a request). */ get uploadProgress(): Progress { let percent; if (this[kBodySize]) { percent = this[kUploadedSize] / this[kBodySize]!; } else if (this[kBodySize] === this[kUploadedSize]) { percent = 1; } else { percent = 0; } return { percent, transferred: this[kUploadedSize], total: this[kBodySize] }; } /** The object contains the following properties: - `start` - Time when the request started. - `socket` - Time when a socket was assigned to the request. - `lookup` - Time when the DNS lookup finished. - `connect` - Time when the socket successfully connected. - `secureConnect` - Time when the socket securely connected. - `upload` - Time when the request finished uploading. - `response` - Time when the request fired `response` event. - `end` - Time when the response fired `end` event. - `error` - Time when the request fired `error` event. - `abort` - Time when the request fired `abort` event. - `phases` - `wait` - `timings.socket - timings.start` - `dns` - `timings.lookup - timings.socket` - `tcp` - `timings.connect - timings.lookup` - `tls` - `timings.secureConnect - timings.connect` - `request` - `timings.upload - (timings.secureConnect || timings.connect)` - `firstByte` - `timings.response - timings.upload` - `download` - `timings.end - timings.response` - `total` - `(timings.end || timings.error || timings.abort) - timings.start` If something has not been measured yet, it will be `undefined`. __Note__: The time is a `number` representing the milliseconds elapsed since the UNIX epoch. */ get timings(): Timings | undefined { return (this[kRequest] as ClientRequestWithTimings)?.timings; } /** Whether the response was retrieved from the cache. */ get isFromCache(): boolean | undefined { return this[kIsFromCache]; } pipe(destination: T, options?: {end?: boolean}): T { if (this[kStartedReading]) { throw new Error('Failed to pipe. The response has been emitted already.'); } if (destination instanceof ServerResponse) { this[kServerResponsesPiped].add(destination); } return super.pipe(destination, options); } unpipe(destination: T): this { if (destination instanceof ServerResponse) { this[kServerResponsesPiped].delete(destination); } super.unpipe(destination); return this; } } got-11.8.5/source/core/utils/000077500000000000000000000000001424347352000157735ustar00rootroot00000000000000got-11.8.5/source/core/utils/dns-ip-version.ts000066400000000000000000000010071424347352000212160ustar00rootroot00000000000000export type DnsLookupIpVersion = 'auto' | 'ipv4' | 'ipv6'; type DnsIpFamily = 0 | 4 | 6; const conversionTable = { auto: 0, ipv4: 4, ipv6: 6 }; export const isDnsLookupIpVersion = (value: any): boolean => { return value in conversionTable; }; export const dnsLookupIpVersionToFamily = (dnsLookupIpVersion: DnsLookupIpVersion): DnsIpFamily => { if (isDnsLookupIpVersion(dnsLookupIpVersion)) { return conversionTable[dnsLookupIpVersion] as DnsIpFamily; } throw new Error('Invalid DNS lookup IP version'); }; got-11.8.5/source/core/utils/get-body-size.ts000066400000000000000000000014571424347352000210340ustar00rootroot00000000000000import {ReadStream, stat} from 'fs'; import {promisify} from 'util'; import {ClientRequestArgs} from 'http'; import is from '@sindresorhus/is'; import isFormData from './is-form-data'; const statAsync = promisify(stat); export default async (body: unknown, headers: ClientRequestArgs['headers']): Promise => { if (headers && 'content-length' in headers) { return Number(headers['content-length']); } if (!body) { return 0; } if (is.string(body)) { return Buffer.byteLength(body); } if (is.buffer(body)) { return body.length; } if (isFormData(body)) { return promisify(body.getLength.bind(body))(); } if (body instanceof ReadStream) { const {size} = await statAsync(body.path); if (size === 0) { return undefined; } return size; } return undefined; }; got-11.8.5/source/core/utils/get-buffer.ts000066400000000000000000000006541424347352000203760ustar00rootroot00000000000000import {Readable} from 'stream'; // TODO: Update https://github.com/sindresorhus/get-stream const getBuffer = async (stream: Readable) => { const chunks = []; let length = 0; for await (const chunk of stream) { chunks.push(chunk); length += Buffer.byteLength(chunk); } if (Buffer.isBuffer(chunks[0])) { return Buffer.concat(chunks, length); } return Buffer.from(chunks.join('')); }; export default getBuffer; got-11.8.5/source/core/utils/is-form-data.ts000066400000000000000000000005221424347352000206250ustar00rootroot00000000000000import is from '@sindresorhus/is'; import {Readable} from 'stream'; interface FormData extends Readable { getBoundary: () => string; getLength: (callback: (error: Error | null, length: number) => void) => void; } export default (body: unknown): body is FormData => is.nodeStream(body) && is.function_((body as FormData).getBoundary); got-11.8.5/source/core/utils/is-response-ok.ts000066400000000000000000000004431424347352000212220ustar00rootroot00000000000000import {Response} from '..'; export const isResponseOk = (response: Response): boolean => { const {statusCode} = response; const limitStatusCode = response.request.options.followRedirect ? 299 : 399; return (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304; }; got-11.8.5/source/core/utils/options-to-url.ts000066400000000000000000000031151424347352000212560ustar00rootroot00000000000000/* istanbul ignore file: deprecated */ import {URL} from 'url'; export interface URLOptions { href?: string; protocol?: string; host?: string; hostname?: string; port?: string | number; pathname?: string; search?: string; searchParams?: unknown; path?: string; } const keys: Array> = [ 'protocol', 'host', 'hostname', 'port', 'pathname', 'search' ]; export default (origin: string, options: URLOptions): URL => { if (options.path) { if (options.pathname) { throw new TypeError('Parameters `path` and `pathname` are mutually exclusive.'); } if (options.search) { throw new TypeError('Parameters `path` and `search` are mutually exclusive.'); } if (options.searchParams) { throw new TypeError('Parameters `path` and `searchParams` are mutually exclusive.'); } } if (options.search && options.searchParams) { throw new TypeError('Parameters `search` and `searchParams` are mutually exclusive.'); } if (!origin) { if (!options.protocol) { throw new TypeError('No URL protocol specified'); } origin = `${options.protocol}//${options.hostname ?? options.host ?? ''}`; } const url = new URL(origin); if (options.path) { const searchIndex = options.path.indexOf('?'); if (searchIndex === -1) { options.pathname = options.path; } else { options.pathname = options.path.slice(0, searchIndex); options.search = options.path.slice(searchIndex + 1); } delete options.path; } for (const key of keys) { if (options[key]) { url[key] = options[key]!.toString(); } } return url; }; got-11.8.5/source/core/utils/proxy-events.ts000066400000000000000000000007121424347352000210260ustar00rootroot00000000000000import {EventEmitter} from 'events'; type Fn = (...args: unknown[]) => void; type Fns = Record; export default function (from: EventEmitter, to: EventEmitter, events: string[]): () => void { const fns: Fns = {}; for (const event of events) { fns[event] = (...args: unknown[]) => { to.emit(event, ...args); }; from.on(event, fns[event]); } return () => { for (const event of events) { from.off(event, fns[event]); } }; } got-11.8.5/source/core/utils/timed-out.ts000066400000000000000000000107641424347352000202620ustar00rootroot00000000000000import net = require('net'); import {ClientRequest, IncomingMessage} from 'http'; import unhandler from './unhandle'; const reentry: unique symbol = Symbol('reentry'); const noop = (): void => {}; interface TimedOutOptions { host?: string; hostname?: string; protocol?: string; } export interface Delays { lookup?: number; connect?: number; secureConnect?: number; socket?: number; response?: number; send?: number; request?: number; } export type ErrorCode = | 'ETIMEDOUT' | 'ECONNRESET' | 'EADDRINUSE' | 'ECONNREFUSED' | 'EPIPE' | 'ENOTFOUND' | 'ENETUNREACH' | 'EAI_AGAIN'; export class TimeoutError extends Error { code: ErrorCode; constructor(threshold: number, public event: string) { super(`Timeout awaiting '${event}' for ${threshold}ms`); this.name = 'TimeoutError'; this.code = 'ETIMEDOUT'; } } export default (request: ClientRequest, delays: Delays, options: TimedOutOptions): () => void => { if (reentry in request) { return noop; } request[reentry] = true; const cancelers: Array = []; const {once, unhandleAll} = unhandler(); const addTimeout = (delay: number, callback: (delay: number, event: string) => void, event: string): (typeof noop) => { const timeout = setTimeout(callback, delay, delay, event) as unknown as NodeJS.Timeout; timeout.unref?.(); const cancel = (): void => { clearTimeout(timeout); }; cancelers.push(cancel); return cancel; }; const {host, hostname} = options; const timeoutHandler = (delay: number, event: string): void => { request.destroy(new TimeoutError(delay, event)); }; const cancelTimeouts = (): void => { for (const cancel of cancelers) { cancel(); } unhandleAll(); }; request.once('error', error => { cancelTimeouts(); // Save original behavior /* istanbul ignore next */ if (request.listenerCount('error') === 0) { throw error; } }); request.once('close', cancelTimeouts); once(request, 'response', (response: IncomingMessage): void => { once(response, 'end', cancelTimeouts); }); if (typeof delays.request !== 'undefined') { addTimeout(delays.request, timeoutHandler, 'request'); } if (typeof delays.socket !== 'undefined') { const socketTimeoutHandler = (): void => { timeoutHandler(delays.socket!, 'socket'); }; request.setTimeout(delays.socket, socketTimeoutHandler); // `request.setTimeout(0)` causes a memory leak. // We can just remove the listener and forget about the timer - it's unreffed. // See https://github.com/sindresorhus/got/issues/690 cancelers.push(() => { request.removeListener('timeout', socketTimeoutHandler); }); } once(request, 'socket', (socket: net.Socket): void => { const {socketPath} = request as ClientRequest & {socketPath?: string}; /* istanbul ignore next: hard to test */ if (socket.connecting) { const hasPath = Boolean(socketPath ?? net.isIP(hostname ?? host ?? '') !== 0); if (typeof delays.lookup !== 'undefined' && !hasPath && typeof (socket.address() as net.AddressInfo).address === 'undefined') { const cancelTimeout = addTimeout(delays.lookup, timeoutHandler, 'lookup'); once(socket, 'lookup', cancelTimeout); } if (typeof delays.connect !== 'undefined') { const timeConnect = (): (() => void) => addTimeout(delays.connect!, timeoutHandler, 'connect'); if (hasPath) { once(socket, 'connect', timeConnect()); } else { once(socket, 'lookup', (error: Error): void => { if (error === null) { once(socket, 'connect', timeConnect()); } }); } } if (typeof delays.secureConnect !== 'undefined' && options.protocol === 'https:') { once(socket, 'connect', (): void => { const cancelTimeout = addTimeout(delays.secureConnect!, timeoutHandler, 'secureConnect'); once(socket, 'secureConnect', cancelTimeout); }); } } if (typeof delays.send !== 'undefined') { const timeRequest = (): (() => void) => addTimeout(delays.send!, timeoutHandler, 'send'); /* istanbul ignore next: hard to test */ if (socket.connecting) { once(socket, 'connect', (): void => { once(request, 'upload-complete', timeRequest()); }); } else { once(request, 'upload-complete', timeRequest()); } } }); if (typeof delays.response !== 'undefined') { once(request, 'upload-complete', (): void => { const cancelTimeout = addTimeout(delays.response!, timeoutHandler, 'response'); once(request, 'response', cancelTimeout); }); } return cancelTimeouts; }; declare module 'http' { interface ClientRequest { [reentry]: boolean; } } got-11.8.5/source/core/utils/unhandle.ts000066400000000000000000000016731424347352000201500ustar00rootroot00000000000000import {EventEmitter} from 'events'; type Origin = EventEmitter; type Event = string | symbol; type Fn = (...args: any[]) => void; interface Handler { origin: Origin; event: Event; fn: Fn; } interface Unhandler { once: (origin: Origin, event: Event, fn: Fn) => void; unhandleAll: () => void; } // When attaching listeners, it's very easy to forget about them. // Especially if you do error handling and set timeouts. // So instead of checking if it's proper to throw an error on every timeout ever, // use this simple tool which will remove all listeners you have attached. export default (): Unhandler => { const handlers: Handler[] = []; return { once(origin: Origin, event: Event, fn: Fn) { origin.once(event, fn); handlers.push({origin, event, fn}); }, unhandleAll() { for (const handler of handlers) { const {origin, event, fn} = handler; origin.removeListener(event, fn); } handlers.length = 0; } }; }; got-11.8.5/source/core/utils/url-to-options.ts000066400000000000000000000017471424347352000212670ustar00rootroot00000000000000import {URL, UrlWithStringQuery} from 'url'; import is from '@sindresorhus/is'; // TODO: Deprecate legacy URL at some point export interface LegacyUrlOptions { protocol: string; hostname: string; host: string; hash: string | null; search: string | null; pathname: string; href: string; path: string; port?: number; auth?: string; } export default (url: URL | UrlWithStringQuery): LegacyUrlOptions => { // Cast to URL url = url as URL; const options: LegacyUrlOptions = { protocol: url.protocol, hostname: is.string(url.hostname) && url.hostname.startsWith('[') ? url.hostname.slice(1, -1) : url.hostname, host: url.host, hash: url.hash, search: url.search, pathname: url.pathname, href: url.href, path: `${url.pathname || ''}${url.search || ''}` }; if (is.string(url.port) && url.port.length > 0) { options.port = Number(url.port); } if (url.username || url.password) { options.auth = `${url.username || ''}:${url.password || ''}`; } return options; }; got-11.8.5/source/core/utils/weakable-map.ts000066400000000000000000000012771424347352000207000ustar00rootroot00000000000000export default class WeakableMap { weakMap: WeakMap, V>; map: Map; constructor() { this.weakMap = new WeakMap(); this.map = new Map(); } set(key: K, value: V): void { if (typeof key === 'object') { this.weakMap.set(key as unknown as Record, value); } else { this.map.set(key, value); } } get(key: K): V | undefined { if (typeof key === 'object') { return this.weakMap.get(key as unknown as Record); } return this.map.get(key); } has(key: K): boolean { if (typeof key === 'object') { return this.weakMap.has(key as unknown as Record); } return this.map.has(key); } } got-11.8.5/source/create.ts000066400000000000000000000205211424347352000155160ustar00rootroot00000000000000import {URL} from 'url'; import is from '@sindresorhus/is'; import asPromise, { // Response Response, // Options Options, NormalizedOptions, // Hooks InitHook, // Errors ParseError, RequestError, CacheError, ReadError, HTTPError, MaxRedirectsError, TimeoutError, UnsupportedProtocolError, UploadError, CancelError } from './as-promise'; import { GotReturn, ExtendOptions, Got, HTTPAlias, HandlerFunction, InstanceDefaults, GotPaginate, GotStream, GotRequestFunction, OptionsWithPagination, StreamOptions } from './types'; import createRejection from './as-promise/create-rejection'; import Request, {kIsNormalizedAlready, setNonEnumerableProperties, Defaults} from './core'; import deepFreeze from './utils/deep-freeze'; const errors = { RequestError, CacheError, ReadError, HTTPError, MaxRedirectsError, TimeoutError, ParseError, CancelError, UnsupportedProtocolError, UploadError }; // The `delay` package weighs 10KB (!) const delay = async (ms: number) => new Promise(resolve => { setTimeout(resolve, ms); }); const {normalizeArguments} = Request; const mergeOptions = (...sources: Options[]): NormalizedOptions => { let mergedOptions: NormalizedOptions | undefined; for (const source of sources) { mergedOptions = normalizeArguments(undefined, source, mergedOptions); } return mergedOptions!; }; const getPromiseOrStream = (options: NormalizedOptions): GotReturn => options.isStream ? new Request(undefined, options) : asPromise(options); const isGotInstance = (value: Got | ExtendOptions): value is Got => ( 'defaults' in value && 'options' in value.defaults ); const aliases: readonly HTTPAlias[] = [ 'get', 'post', 'put', 'patch', 'head', 'delete' ]; export const defaultHandler: HandlerFunction = (options, next) => next(options); const callInitHooks = (hooks: InitHook[] | undefined, options?: Options): void => { if (hooks) { for (const hook of hooks) { hook(options!); } } }; const create = (defaults: InstanceDefaults): Got => { // Proxy properties from next handlers defaults._rawHandlers = defaults.handlers; defaults.handlers = defaults.handlers.map(fn => ((options, next) => { // This will be assigned by assigning result let root!: ReturnType; const result = fn(options, newOptions => { root = next(newOptions); return root; }); if (result !== root && !options.isStream && root) { const typedResult = result as Promise; const {then: promiseThen, catch: promiseCatch, finally: promiseFianlly} = typedResult; Object.setPrototypeOf(typedResult, Object.getPrototypeOf(root)); Object.defineProperties(typedResult, Object.getOwnPropertyDescriptors(root)); // These should point to the new promise // eslint-disable-next-line promise/prefer-await-to-then typedResult.then = promiseThen; typedResult.catch = promiseCatch; typedResult.finally = promiseFianlly; } return result; })); // Got interface const got: Got = ((url: string | URL, options: Options = {}, _defaults?: Defaults): GotReturn => { let iteration = 0; const iterateHandlers = (newOptions: NormalizedOptions): GotReturn => { return defaults.handlers[iteration++]( newOptions, iteration === defaults.handlers.length ? getPromiseOrStream : iterateHandlers ) as GotReturn; }; // TODO: Remove this in Got 12. if (is.plainObject(url)) { const mergedOptions = { ...url as Options, ...options }; setNonEnumerableProperties([url as Options, options], mergedOptions); options = mergedOptions; url = undefined as any; } try { // Call `init` hooks let initHookError: Error | undefined; try { callInitHooks(defaults.options.hooks.init, options); callInitHooks(options.hooks?.init, options); } catch (error) { initHookError = error; } // Normalize options & call handlers const normalizedOptions = normalizeArguments(url, options, _defaults ?? defaults.options); normalizedOptions[kIsNormalizedAlready] = true; if (initHookError) { throw new RequestError(initHookError.message, initHookError, normalizedOptions); } return iterateHandlers(normalizedOptions); } catch (error) { if (options.isStream) { throw error; } else { return createRejection(error, defaults.options.hooks.beforeError, options.hooks?.beforeError); } } }) as Got; got.extend = (...instancesOrOptions) => { const optionsArray: Options[] = [defaults.options]; let handlers: HandlerFunction[] = [...defaults._rawHandlers!]; let isMutableDefaults: boolean | undefined; for (const value of instancesOrOptions) { if (isGotInstance(value)) { optionsArray.push(value.defaults.options); handlers.push(...value.defaults._rawHandlers!); isMutableDefaults = value.defaults.mutableDefaults; } else { optionsArray.push(value); if ('handlers' in value) { handlers.push(...value.handlers!); } isMutableDefaults = value.mutableDefaults; } } handlers = handlers.filter(handler => handler !== defaultHandler); if (handlers.length === 0) { handlers.push(defaultHandler); } return create({ options: mergeOptions(...optionsArray), handlers, mutableDefaults: Boolean(isMutableDefaults) }); }; // Pagination const paginateEach = (async function * (url: string | URL, options?: OptionsWithPagination): AsyncIterableIterator { // TODO: Remove this `@ts-expect-error` when upgrading to TypeScript 4. // Error: Argument of type 'Merge> | undefined' is not assignable to parameter of type 'Options | undefined'. // @ts-expect-error let normalizedOptions = normalizeArguments(url, options, defaults.options); normalizedOptions.resolveBodyOnly = false; const pagination = normalizedOptions.pagination!; if (!is.object(pagination)) { throw new TypeError('`options.pagination` must be implemented'); } const all: T[] = []; let {countLimit} = pagination; let numberOfRequests = 0; while (numberOfRequests < pagination.requestLimit) { if (numberOfRequests !== 0) { // eslint-disable-next-line no-await-in-loop await delay(pagination.backoff); } // @ts-expect-error FIXME! // TODO: Throw when result is not an instance of Response // eslint-disable-next-line no-await-in-loop const result = (await got(undefined, undefined, normalizedOptions)) as Response; // eslint-disable-next-line no-await-in-loop const parsed = await pagination.transform(result); const current: T[] = []; for (const item of parsed) { if (pagination.filter(item, all, current)) { if (!pagination.shouldContinue(item, all, current)) { return; } yield item as T; if (pagination.stackAllItems) { all.push(item as T); } current.push(item as T); if (--countLimit <= 0) { return; } } } const optionsToMerge = pagination.paginate(result, all, current); if (optionsToMerge === false) { return; } if (optionsToMerge === result.request.options) { normalizedOptions = result.request.options; } else if (optionsToMerge !== undefined) { normalizedOptions = normalizeArguments(undefined, optionsToMerge, normalizedOptions); } numberOfRequests++; } }); got.paginate = paginateEach as GotPaginate; got.paginate.all = (async (url: string | URL, options?: OptionsWithPagination) => { const results: T[] = []; for await (const item of paginateEach(url, options)) { results.push(item); } return results; }) as GotPaginate['all']; // For those who like very descriptive names got.paginate.each = paginateEach as GotPaginate['each']; // Stream API got.stream = ((url: string | URL, options?: StreamOptions) => got(url, {...options, isStream: true})) as GotStream; // Shortcuts for (const method of aliases) { got[method] = ((url: string | URL, options?: Options): GotReturn => got(url, {...options, method})) as GotRequestFunction; got.stream[method] = ((url: string | URL, options?: StreamOptions) => { return got(url, {...options, method, isStream: true}); }) as GotStream; } Object.assign(got, errors); Object.defineProperty(got, 'defaults', { value: defaults.mutableDefaults ? defaults : deepFreeze(defaults), writable: defaults.mutableDefaults, configurable: defaults.mutableDefaults, enumerable: true }); got.mergeOptions = mergeOptions; return got; }; export default create; export * from './types'; got-11.8.5/source/index.ts000066400000000000000000000052351424347352000153670ustar00rootroot00000000000000import {URL} from 'url'; import {Response, Options} from './as-promise'; import create, {defaultHandler, InstanceDefaults} from './create'; const defaults: InstanceDefaults = { options: { method: 'GET', retry: { limit: 2, methods: [ 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE' ], statusCodes: [ 408, 413, 429, 500, 502, 503, 504, 521, 522, 524 ], errorCodes: [ 'ETIMEDOUT', 'ECONNRESET', 'EADDRINUSE', 'ECONNREFUSED', 'EPIPE', 'ENOTFOUND', 'ENETUNREACH', 'EAI_AGAIN' ], maxRetryAfter: undefined, calculateDelay: ({computedValue}) => computedValue }, timeout: {}, headers: { 'user-agent': 'got (https://github.com/sindresorhus/got)' }, hooks: { init: [], beforeRequest: [], beforeRedirect: [], beforeRetry: [], beforeError: [], afterResponse: [] }, cache: undefined, dnsCache: undefined, decompress: true, throwHttpErrors: true, followRedirect: true, isStream: false, responseType: 'text', resolveBodyOnly: false, maxRedirects: 10, prefixUrl: '', methodRewriting: true, ignoreInvalidCookies: false, context: {}, // TODO: Set this to `true` when Got 12 gets released http2: false, allowGetBody: false, https: undefined, pagination: { transform: (response: Response) => { if (response.request.options.responseType === 'json') { return response.body; } return JSON.parse(response.body as string); }, paginate: response => { if (!Reflect.has(response.headers, 'link')) { return false; } const items = (response.headers.link as string).split(','); let next: string | undefined; for (const item of items) { const parsed = item.split(';'); if (parsed[1].includes('next')) { next = parsed[0].trimStart().trim(); next = next.slice(1, -1); break; } } if (next) { const options: Options = { url: new URL(next) }; return options; } return false; }, filter: () => true, shouldContinue: () => true, countLimit: Infinity, backoff: 0, requestLimit: 10000, stackAllItems: true }, parseJson: (text: string) => JSON.parse(text), stringifyJson: (object: unknown) => JSON.stringify(object), cacheOptions: {} }, handlers: [defaultHandler], mutableDefaults: false }; const got = create(defaults); export default got; // For CommonJS default export support module.exports = got; module.exports.default = got; module.exports.__esModule = true; // Workaround for TS issue: https://github.com/sindresorhus/got/pull/1267 export * from './create'; export * from './as-promise'; got-11.8.5/source/types.ts000066400000000000000000000316331424347352000154250ustar00rootroot00000000000000import {URL} from 'url'; import {CancelError} from 'p-cancelable'; import { // Request & Response CancelableRequest, Response, // Options Options, NormalizedOptions, Defaults as DefaultOptions, PaginationOptions, // Errors ParseError, RequestError, CacheError, ReadError, HTTPError, MaxRedirectsError, TimeoutError, UnsupportedProtocolError, UploadError } from './as-promise'; import Request from './core'; // `type-fest` utilities type Except = Pick>; type Merge = Except> & SecondType; /** Defaults for each Got instance. */ export interface InstanceDefaults { /** An object containing the default options of Got. */ options: DefaultOptions; /** An array of functions. You execute them directly by calling `got()`. They are some sort of "global hooks" - these functions are called first. The last handler (*it's hidden*) is either `asPromise` or `asStream`, depending on the `options.isStream` property. @default [] */ handlers: HandlerFunction[]; /** A read-only boolean describing whether the defaults are mutable or not. If set to `true`, you can update headers over time, for example, update an access token when it expires. @default false */ mutableDefaults: boolean; _rawHandlers?: HandlerFunction[]; } /** A Request object returned by calling Got, or any of the Got HTTP alias request functions. */ export type GotReturn = Request | CancelableRequest; /** A function to handle options and returns a Request object. It acts sort of like a "global hook", and will be called before any actual request is made. */ export type HandlerFunction = (options: NormalizedOptions, next: (options: NormalizedOptions) => T) => T | Promise; /** The options available for `got.extend()`. */ export interface ExtendOptions extends Options { /** An array of functions. You execute them directly by calling `got()`. They are some sort of "global hooks" - these functions are called first. The last handler (*it's hidden*) is either `asPromise` or `asStream`, depending on the `options.isStream` property. @default [] */ handlers?: HandlerFunction[]; /** A read-only boolean describing whether the defaults are mutable or not. If set to `true`, you can update headers over time, for example, update an access token when it expires. @default false */ mutableDefaults?: boolean; } export type OptionsOfTextResponseBody = Merge; export type OptionsOfJSONResponseBody = Merge; export type OptionsOfBufferResponseBody = Merge; export type OptionsOfUnknownResponseBody = Merge; export type StrictOptions = Except; export type StreamOptions = Merge; type ResponseBodyOnly = {resolveBodyOnly: true}; export type OptionsWithPagination = Merge>; /** An instance of `got.paginate`. */ export interface GotPaginate { /** Same as `GotPaginate.each`. */ (url: string | URL, options?: OptionsWithPagination): AsyncIterableIterator; /** Same as `GotPaginate.each`. */ (options?: OptionsWithPagination): AsyncIterableIterator; /** Returns an async iterator. See pagination.options for more pagination options. @example ``` (async () => { const countLimit = 10; const pagination = got.paginate('https://api.github.com/repos/sindresorhus/got/commits', { pagination: {countLimit} }); console.log(`Printing latest ${countLimit} Got commits (newest to oldest):`); for await (const commitData of pagination) { console.log(commitData.commit.message); } })(); ``` */ each: ((url: string | URL, options?: OptionsWithPagination) => AsyncIterableIterator) & ((options?: OptionsWithPagination) => AsyncIterableIterator); /** Returns a Promise for an array of all results. See pagination.options for more pagination options. @example ``` (async () => { const countLimit = 10; const results = await got.paginate.all('https://api.github.com/repos/sindresorhus/got/commits', { pagination: {countLimit} }); console.log(`Printing latest ${countLimit} Got commits (newest to oldest):`); console.log(results); })(); ``` */ all: ((url: string | URL, options?: OptionsWithPagination) => Promise) & ((options?: OptionsWithPagination) => Promise); } export interface GotRequestFunction { // `asPromise` usage (url: string | URL, options?: OptionsOfTextResponseBody): CancelableRequest>; (url: string | URL, options?: OptionsOfJSONResponseBody): CancelableRequest>; (url: string | URL, options?: OptionsOfBufferResponseBody): CancelableRequest>; (url: string | URL, options?: OptionsOfUnknownResponseBody): CancelableRequest; (options: OptionsOfTextResponseBody): CancelableRequest>; (options: OptionsOfJSONResponseBody): CancelableRequest>; (options: OptionsOfBufferResponseBody): CancelableRequest>; (options: OptionsOfUnknownResponseBody): CancelableRequest; // `resolveBodyOnly` usage (url: string | URL, options?: (Merge)): CancelableRequest; (url: string | URL, options?: (Merge)): CancelableRequest; (url: string | URL, options?: (Merge)): CancelableRequest; (options: (Merge)): CancelableRequest; (options: (Merge)): CancelableRequest; (options: (Merge)): CancelableRequest; // `asStream` usage (url: string | URL, options?: Merge): Request; (options: Merge): Request; // Fallback (url: string | URL, options?: Options): CancelableRequest | Request; (options: Options): CancelableRequest | Request; } /** All available HTTP request methods provided by Got. */ export type HTTPAlias = | 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete'; interface GotStreamFunction { (url: string | URL, options?: Merge): Request; (options?: Merge): Request; } /** An instance of `got.stream()`. */ export type GotStream = GotStreamFunction & Record; /** An instance of `got`. */ export interface Got extends Record, GotRequestFunction { /** Sets `options.isStream` to `true`. Returns a [duplex stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) with additional events: - request - response - redirect - uploadProgress - downloadProgress - error */ stream: GotStream; /** Returns an async iterator. See pagination.options for more pagination options. @example ``` (async () => { const countLimit = 10; const pagination = got.paginate('https://api.github.com/repos/sindresorhus/got/commits', { pagination: {countLimit} }); console.log(`Printing latest ${countLimit} Got commits (newest to oldest):`); for await (const commitData of pagination) { console.log(commitData.commit.message); } })(); ``` */ paginate: GotPaginate; /** The Got defaults used in that instance. */ defaults: InstanceDefaults; /** An error to be thrown when a cache method fails. For example, if the database goes down or there's a filesystem error. Contains a `code` property with `ERR_CACHE_ACCESS` or a more specific failure code. */ CacheError: typeof CacheError; /** An error to be thrown when a request fails. Contains a `code` property with error class code, like `ECONNREFUSED`. If there is no specific code supplied, `code` defaults to `ERR_GOT_REQUEST_ERROR`. */ RequestError: typeof RequestError; /** An error to be thrown when reading from response stream fails. Contains a `code` property with `ERR_READING_RESPONSE_STREAM` or a more specific failure code. */ ReadError: typeof ReadError; /** An error to be thrown when server response code is 2xx, and parsing body fails. Includes a `response` property. Contains a `code` property with `ERR_BODY_PARSE_FAILURE` or a more specific failure code. */ ParseError: typeof ParseError; /** An error to be thrown when the server response code is not 2xx nor 3xx if `options.followRedirect` is `true`, but always except for 304. Includes a `response` property. Contains a `code` property with `ERR_NON_2XX_3XX_RESPONSE` or a more specific failure code. */ HTTPError: typeof HTTPError; /** An error to be thrown when the server redirects you more than ten times. Includes a `response` property. Contains a `code` property with `ERR_TOO_MANY_REDIRECTS`. */ MaxRedirectsError: typeof MaxRedirectsError; /** An error to be thrown when given an unsupported protocol. Contains a `code` property with `ERR_UNSUPPORTED_PROTOCOL`. */ UnsupportedProtocolError: typeof UnsupportedProtocolError; /** An error to be thrown when the request is aborted due to a timeout. Includes an `event` and `timings` property. Contains a `code` property with `ETIMEDOUT`. */ TimeoutError: typeof TimeoutError; /** An error to be thrown when the request body is a stream and an error occurs while reading from that stream. Contains a `code` property with `ERR_UPLOAD` or a more specific failure code. */ UploadError: typeof UploadError; /** An error to be thrown when the request is aborted with `.cancel()`. Contains a `code` property with `ERR_CANCELED`. */ CancelError: typeof CancelError; /** Configure a new `got` instance with default `options`. The `options` are merged with the parent instance's `defaults.options` using `got.mergeOptions`. You can access the resolved options with the `.defaults` property on the instance. Additionally, `got.extend()` accepts two properties from the `defaults` object: `mutableDefaults` and `handlers`. It is also possible to merges many instances into a single one: - options are merged using `got.mergeOptions()` (including hooks), - handlers are stored in an array (you can access them through `instance.defaults.handlers`). @example ```js const client = got.extend({ prefixUrl: 'https://example.com', headers: { 'x-unicorn': 'rainbow' } }); client.get('demo'); // HTTP Request => // GET /demo HTTP/1.1 // Host: example.com // x-unicorn: rainbow ``` */ extend: (...instancesOrOptions: Array) => Got; /** Merges multiple `got` instances into the parent. */ mergeInstances: (parent: Got, ...instances: Got[]) => Got; /** Extends parent options. Avoid using [object spread](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals) as it doesn't work recursively. Options are deeply merged to a new object. The value of each key is determined as follows: - If the new property is not defined, the old value is used. - If the new property is explicitly set to `undefined`: - If the parent property is a plain `object`, the parent value is deeply cloned. - Otherwise, `undefined` is used. - If the parent value is an instance of `URLSearchParams`: - If the new value is a `string`, an `object` or an instance of `URLSearchParams`, a new `URLSearchParams` instance is created. The values are merged using [`urlSearchParams.append(key, value)`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/append). The keys defined in the new value override the keys defined in the parent value. - Otherwise, the only available value is `undefined`. - If the new property is a plain `object`: - If the parent property is a plain `object` too, both values are merged recursively into a new `object`. - Otherwise, only the new value is deeply cloned. - If the new property is an `Array`, it overwrites the old one with a deep clone of the new property. - Properties that are not enumerable, such as `context`, `body`, `json`, and `form`, will not be merged. - Otherwise, the new value is assigned to the key. **Note:** Only Got options are merged! Custom user options should be defined via [`options.context`](#context). @example ``` const a = {headers: {cat: 'meow', wolf: ['bark', 'wrrr']}}; const b = {headers: {cow: 'moo', wolf: ['auuu']}}; {...a, ...b} // => {headers: {cow: 'moo', wolf: ['auuu']}} got.mergeOptions(a, b) // => {headers: {cat: 'meow', cow: 'moo', wolf: ['auuu']}} ``` */ mergeOptions: (...sources: Options[]) => NormalizedOptions; } got-11.8.5/source/utils/000077500000000000000000000000001424347352000150435ustar00rootroot00000000000000got-11.8.5/source/utils/deep-freeze.ts000066400000000000000000000004371424347352000176120ustar00rootroot00000000000000import is from '@sindresorhus/is'; export default function deepFreeze>(object: T): Readonly { for (const value of Object.values(object)) { if (is.plainObject(value) || is.array(value)) { deepFreeze(value); } } return Object.freeze(object); } got-11.8.5/source/utils/deprecation-warning.ts000066400000000000000000000004271424347352000213560ustar00rootroot00000000000000const alreadyWarned: Set = new Set(); export default (message: string) => { if (alreadyWarned.has(message)) { return; } alreadyWarned.add(message); // @ts-expect-error Missing types. process.emitWarning(`Got: ${message}`, { type: 'DeprecationWarning' }); }; got-11.8.5/test/000077500000000000000000000000001424347352000133625ustar00rootroot00000000000000got-11.8.5/test/agent.ts000066400000000000000000000115141424347352000150320ustar00rootroot00000000000000import {Agent as HttpAgent} from 'http'; import {Agent as HttpsAgent} from 'https'; import {Socket} from 'net'; import test, {Constructor} from 'ava'; import sinon = require('sinon'); import withServer, {withHttpsServer} from './helpers/with-server'; const createAgentSpy = (AgentClass: Constructor): {agent: T; spy: sinon.SinonSpy} => { const agent: T = new AgentClass({keepAlive: true}); // @ts-expect-error This IS correct const spy = sinon.spy(agent, 'addRequest'); return {agent, spy}; }; test('non-object agent option works with http', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {agent, spy} = createAgentSpy(HttpAgent); t.truthy((await got({ https: { rejectUnauthorized: false }, agent: { http: agent } })).body); t.true(spy.calledOnce); // Make sure to close all open sockets agent.destroy(); }); test('non-object agent option works with https', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {agent, spy} = createAgentSpy(HttpsAgent); t.truthy((await got({ https: { rejectUnauthorized: false }, agent: { https: agent } })).body); t.true(spy.calledOnce); // Make sure to close all open sockets agent.destroy(); }); test('redirects from http to https work with an agent object', withServer, async (t, serverHttp) => { await withHttpsServer()(t, async (t, serverHttps, got) => { serverHttp.get('/', (_request, response) => { response.end('http'); }); serverHttps.get('/', (_request, response) => { response.end('https'); }); serverHttp.get('/httpToHttps', (_request, response) => { response.writeHead(302, { location: serverHttps.url }); response.end(); }); const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent); const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent); t.truthy((await got('httpToHttps', { prefixUrl: serverHttp.url, agent: { http: httpAgent, https: httpsAgent } })).body); t.true(httpSpy.calledOnce); t.true(httpsSpy.calledOnce); // Make sure to close all open sockets httpAgent.destroy(); httpsAgent.destroy(); }); }); test('redirects from https to http work with an agent object', withHttpsServer(), async (t, serverHttps, got) => { await withServer(t, async (t, serverHttp) => { serverHttp.get('/', (_request, response) => { response.end('http'); }); serverHttps.get('/', (_request, response) => { response.end('https'); }); serverHttps.get('/httpsToHttp', (_request, response) => { response.writeHead(302, { location: serverHttp.url }); response.end(); }); const {agent: httpAgent, spy: httpSpy} = createAgentSpy(HttpAgent); const {agent: httpsAgent, spy: httpsSpy} = createAgentSpy(HttpsAgent); t.truthy((await got('httpsToHttp', { prefixUrl: serverHttps.url, agent: { http: httpAgent, https: httpsAgent } })).body); t.true(httpSpy.calledOnce); t.true(httpsSpy.calledOnce); // Make sure to close all open sockets httpAgent.destroy(); httpsAgent.destroy(); }); }); test('socket connect listener cleaned up after request', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {agent} = createAgentSpy(HttpsAgent); // Make sure there are no memory leaks when reusing keep-alive sockets for (let i = 0; i < 20; i++) { // eslint-disable-next-line no-await-in-loop await got({ agent: { https: agent } }); } // Node.js 12 has incomplete types for (const value of Object.values((agent as any).freeSockets) as [Socket[]]) { for (const sock of value) { t.is(sock.listenerCount('connect'), 0); } } // Make sure to close all open sockets agent.destroy(); }); { const testFn = Number(process.versions.node.split('.')[0]) < 12 ? test.failing : test; testFn('no socket hung up regression', withServer, async (t, server, got) => { const agent = new HttpAgent({keepAlive: true}); const token = 'helloworld'; server.get('/', (request, response) => { if (request.headers.token !== token) { response.statusCode = 401; response.end(); return; } response.end('ok'); }); const {body} = await got({ prefixUrl: 'http://127.0.0.1:3000', agent: { http: agent }, hooks: { afterResponse: [ async (response, retryWithMergedOptions) => { // Force clean-up response.socket?.destroy(); // Unauthorized if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token } }); } // No changes otherwise return response; } ] }, // Disable automatic retries, manual retries are allowed retry: 0 }); t.is(body, 'ok'); agent.destroy(); }); } got-11.8.5/test/arguments.ts000066400000000000000000000324241424347352000157440ustar00rootroot00000000000000/* eslint-disable node/no-deprecated-api */ import {parse, URL, URLSearchParams} from 'url'; import test from 'ava'; import {Handler} from 'express'; import pEvent = require('p-event'); import got, {StrictOptions} from '../source'; import withServer, {withBodyParsingServer} from './helpers/with-server'; const echoUrl: Handler = (request, response) => { response.end(request.url); }; test('`url` is required', async t => { await t.throwsAsync( // @ts-expect-error No argument on purpose. got(), { message: 'Missing `url` property' } ); await t.throwsAsync( got(''), { message: 'No URL protocol specified' } ); await t.throwsAsync( got({ url: '' }), { message: 'No URL protocol specified' } ); }); test('`url` should be utf-8 encoded', async t => { await t.throwsAsync( got('https://example.com/%D2%E0%EB%EB%E8%ED'), { message: 'URI malformed' } ); }); test('throws if no arguments provided', async t => { // @ts-expect-error Error tests await t.throwsAsync(got(), { message: 'Missing `url` property' }); }); test('throws an error if the protocol is not specified', async t => { await t.throwsAsync(got('example.com'), { instanceOf: TypeError, message: 'Invalid URL: example.com' }); await t.throwsAsync(got({}), { message: 'Missing `url` property' }); }); test('properly encodes query string', withServer, async (t, server, got) => { server.get('/', echoUrl); const path = '?test=http://example.com?foo=bar'; const {body} = await got(path); t.is(body, '/?test=http://example.com?foo=bar'); }); test('options are optional', withServer, async (t, server, got) => { server.get('/test', echoUrl); t.is((await got('test')).body, '/test'); }); test('methods are normalized', withServer, async (t, server, got) => { server.post('/test', echoUrl); const instance = got.extend({ handlers: [ (options, next) => { if (options.method === options.method.toUpperCase()) { t.pass(); } else { t.fail(); } return next(options); } ] }); await instance('test', {method: 'post'}); }); test.failing('throws an error when legacy URL is passed', withServer, async (t, server) => { server.get('/test', echoUrl); await t.throwsAsync( // @ts-expect-error Error tests got(parse(`${server.url}/test`)), {message: 'The legacy `url.Url` has been deprecated. Use `URL` instead.'} ); await t.throwsAsync( got({ protocol: 'http:', hostname: 'localhost', port: server.port }), {message: 'The legacy `url.Url` has been deprecated. Use `URL` instead.'} ); }); test('accepts legacy URL options', withServer, async (t, server) => { server.get('/test', echoUrl); const {body: secondBody} = await got({ protocol: 'http:', hostname: 'localhost', port: server.port, pathname: '/test' }); t.is(secondBody, '/test'); }); test('overrides `searchParams` from options', withServer, async (t, server, got) => { server.get('/', echoUrl); const {body} = await got( '?drop=this', { searchParams: { test: 'wow' } } ); t.is(body, '/?test=wow'); }); test('does not duplicate `searchParams`', withServer, async (t, server, got) => { server.get('/', echoUrl); const instance = got.extend({ searchParams: new URLSearchParams({foo: '123'}) }); const body = await instance('?bar=456').text(); t.is(body, '/?foo=123'); }); test('escapes `searchParams` parameter values', withServer, async (t, server, got) => { server.get('/', echoUrl); const {body} = await got({ searchParams: { test: 'it’s ok' } }); t.is(body, '/?test=it%E2%80%99s+ok'); }); test('the `searchParams` option can be a URLSearchParams', withServer, async (t, server, got) => { server.get('/', echoUrl); const searchParameters = new URLSearchParams({test: 'wow'}); const {body} = await got({searchParams: searchParameters}); t.is(body, '/?test=wow'); }); test('ignores empty searchParams object', withServer, async (t, server, got) => { server.get('/test', echoUrl); t.is((await got('test', {searchParams: {}})).requestUrl, `${server.url}/test`); }); test('throws when passing body with a non payload method', async t => { await t.throwsAsync(got('https://example.com', {body: 'asdf'}), { message: 'The `GET` method cannot be used with a body' }); }); test('`allowGetBody` option', withServer, async (t, server, got) => { server.get('/test', echoUrl); const url = new URL(`${server.url}/test`); await t.notThrowsAsync(got(url, {body: 'asdf', allowGetBody: true})); }); test('WHATWG URL support', withServer, async (t, server, got) => { server.get('/test', echoUrl); const url = new URL(`${server.url}/test`); await t.notThrowsAsync(got(url)); }); test('returns streams when using `isStream` option', withServer, async (t, server, got) => { server.get('/stream', (_request, response) => { response.end('ok'); }); const data = await pEvent(got('stream', {isStream: true}), 'data'); t.is(data.toString(), 'ok'); }); test('accepts `url` as an option', withServer, async (t, server, got) => { server.get('/test', echoUrl); await t.notThrowsAsync(got({url: 'test'})); }); test('can omit `url` option if using `prefixUrl`', withServer, async (t, server, got) => { server.get('/', echoUrl); await t.notThrowsAsync(got({})); }); test('throws TypeError when known `options.hooks` value is not an array', async t => { await t.throwsAsync( // @ts-expect-error Error tests got('https://example.com', {hooks: {beforeRequest: {}}}), { message: 'Parameter `beforeRequest` must be an Array, got Object' } ); }); test('throws TypeError when known `options.hooks` array item is not a function', async t => { await t.throwsAsync( // @ts-expect-error Error tests got('https://example.com', {hooks: {beforeRequest: [{}]}}), { message: 'hook is not a function' } ); }); test('allows extra keys in `options.hooks`', withServer, async (t, server, got) => { server.get('/test', echoUrl); // @ts-expect-error We do not allow extra keys in hooks but this won't throw await t.notThrowsAsync(got('test', {hooks: {extra: []}})); }); test('`prefixUrl` option works', withServer, async (t, server, got) => { server.get('/test/foobar', echoUrl); const instanceA = got.extend({prefixUrl: `${server.url}/test`}); const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); test('accepts WHATWG URL as the `prefixUrl` option', withServer, async (t, server, got) => { server.get('/test/foobar', echoUrl); const instanceA = got.extend({prefixUrl: new URL(`${server.url}/test`)}); const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); test('backslash in the end of `prefixUrl` option is optional', withServer, async (t, server) => { server.get('/test/foobar', echoUrl); const instanceA = got.extend({prefixUrl: `${server.url}/test/`}); const {body} = await instanceA('foobar'); t.is(body, '/test/foobar'); }); test('`prefixUrl` can be changed if the URL contains the old one', withServer, async (t, server) => { server.get('/', echoUrl); const instanceA = got.extend({ prefixUrl: `${server.url}/meh`, handlers: [ (options, next) => { options.prefixUrl = server.url; return next(options); } ] }); const {body} = await instanceA(''); t.is(body, '/'); }); test('throws if cannot change `prefixUrl`', async t => { const instanceA = got.extend({ prefixUrl: 'https://example.com', handlers: [ (options, next) => { options.url = new URL('https://google.pl'); options.prefixUrl = 'https://example.com'; return next(options); } ] }); await t.throwsAsync(instanceA(''), {message: 'Cannot change `prefixUrl` from https://example.com/ to https://example.com: https://google.pl/'}); }); test('throws if the `searchParams` value is invalid', async t => { await t.throwsAsync(got('https://example.com', { searchParams: { // @ts-expect-error Error tests foo: [] } }), { instanceOf: TypeError, message: 'The `searchParams` value \'\' must be a string, number, boolean or null' }); }); test('`context` option is not enumerable', withServer, async (t, server, got) => { server.get('/', echoUrl); const context = { foo: 'bar' }; await got({ context, hooks: { beforeRequest: [ options => { t.is(options.context, context); t.false({}.propertyIsEnumerable.call(options, 'context')); } ] } }); }); test('`context` option is accessible when using hooks', withServer, async (t, server, got) => { server.get('/', echoUrl); const context = { foo: 'bar' }; await got({ context, hooks: { beforeRequest: [ options => { t.is(options.context, context); t.false({}.propertyIsEnumerable.call(options, 'context')); } ] } }); }); test('`context` option is accessible when extending instances', t => { const context = { foo: 'bar' }; const instance = got.extend({context}); t.is(instance.defaults.options.context, context); t.false({}.propertyIsEnumerable.call(instance.defaults.options, 'context')); }); test('throws if `options.encoding` is `null`', async t => { await t.throwsAsync(got('https://example.com', { // @ts-expect-error For testing purposes encoding: null }), {message: 'To get a Buffer, set `options.responseType` to `buffer` instead'}); }); test('`url` option and input argument are mutually exclusive', async t => { await t.throwsAsync(got('https://example.com', { url: 'https://example.com' }), {message: 'The `url` option is mutually exclusive with the `input` argument'}); }); test('throws a helpful error when passing `followRedirects`', async t => { await t.throwsAsync(got('https://example.com', { // @ts-expect-error For testing purposes followRedirects: true }), {message: 'The `followRedirects` option does not exist. Use `followRedirect` instead.'}); }); test('merges `searchParams` instances', t => { const instance = got.extend({ searchParams: new URLSearchParams('a=1') }, { searchParams: new URLSearchParams('b=2') }); t.is(instance.defaults.options.searchParams!.get('a'), '1'); t.is(instance.defaults.options.searchParams!.get('b'), '2'); }); test('throws a helpful error when passing `auth`', async t => { await t.throwsAsync(got('https://example.com', { // @ts-expect-error For testing purposes auth: 'username:password' }), { message: 'Parameter `auth` is deprecated. Use `username` / `password` instead.' }); }); test('throws on leading slashes', async t => { await t.throwsAsync(got('/asdf', {prefixUrl: 'https://example.com'}), { message: '`input` must not start with a slash when using `prefixUrl`' }); }); test('throws on invalid `dnsCache` option', async t => { await t.throwsAsync(got('https://example.com', { // @ts-expect-error Error tests dnsCache: 123 }), {message: 'Parameter `dnsCache` must be a CacheableLookup instance or a boolean, got number'}); }); test('throws on invalid `agent` option', async t => { await t.throwsAsync(got('https://example.com', { agent: { // @ts-expect-error Error tests asdf: 123 } }), {message: 'Expected the `options.agent` properties to be `http`, `https` or `http2`, got `asdf`'}); }); test('fallbacks to native http if `request(...)` returns undefined', withServer, async (t, server, got) => { server.get('/', echoUrl); const {body} = await got('', {request: () => undefined}); t.is(body, '/'); }); test('strict options', withServer, async (t, server, got) => { server.get('/', echoUrl); const options: StrictOptions = {}; const {body} = await got(options); t.is(body, '/'); }); test('does not throw on frozen options', withServer, async (t, server, got) => { server.get('/', echoUrl); const options: StrictOptions = {}; Object.freeze(options); const {body} = await got(options); t.is(body, '/'); }); test('encodes query string included in input', t => { const {url} = got.mergeOptions({ url: new URL('https://example.com/?a=b c') }); t.is(url.search, '?a=b%20c'); }); test('normalizes search params included in options', t => { const {url} = got.mergeOptions({ url: new URL('https://example.com'), searchParams: 'a=b c' }); t.is(url.search, '?a=b+c'); }); test('reuse options while using init hook', withServer, async (t, server, got) => { t.plan(2); server.get('/', echoUrl); const options = { hooks: { init: [ () => { t.pass(); } ] } }; await got('', options); await got('', options); }); test('allowGetBody sends json payload', withBodyParsingServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.body.hello !== 'world') { response.statusCode = 400; } response.end(); }); const {statusCode} = await got({ allowGetBody: true, json: {hello: 'world'}, retry: 0, throwHttpErrors: false }); t.is(statusCode, 200); }); test('no URL pollution', withServer, async (t, server) => { server.get('/ok', echoUrl); const url = new URL(server.url); const {body} = await got(url, { hooks: { beforeRequest: [ options => { options.url.pathname = '/ok'; } ] } }); t.is(url.pathname, '/'); t.is(body, '/ok'); }); test('prefixUrl is properly replaced when extending', withServer, async (t, server) => { server.get('/', (request, response) => { response.end(request.url); }); server.get('/other/path/', (request, response) => { response.end(request.url); }); const parent = got.extend({prefixUrl: server.url}); const child = parent.extend({prefixUrl: `${server.url}/other/path/`}); t.is(await child.get('').text(), '/other/path/'); }); got-11.8.5/test/cache.ts000066400000000000000000000217671424347352000150120ustar00rootroot00000000000000import {promisify} from 'util'; import {gzip} from 'zlib'; import test from 'ava'; import pEvent = require('p-event'); import getStream = require('get-stream'); import {Handler} from 'express'; import got, {Response} from '../source'; import withServer from './helpers/with-server'; import CacheableLookup from 'cacheable-lookup'; import delay = require('delay'); const cacheEndpoint: Handler = (_request, response) => { response.setHeader('Cache-Control', 'public, max-age=60'); response.end(Date.now().toString()); }; test('non-cacheable responses are not cached', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Cache-Control', 'public, no-cache, no-store'); response.end(Date.now().toString()); }); const cache = new Map(); const firstResponseInt = Number((await got({cache})).body); const secondResponseInt = Number((await got({cache})).body); t.is(cache.size, 0); t.true(firstResponseInt < secondResponseInt); }); test('cacheable responses are cached', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const firstResponse = await got({cache}); const secondResponse = await got({cache}); t.is(cache.size, 1); t.is(firstResponse.body, secondResponse.body); }); test('cached response is re-encoded to current encoding option', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const firstEncoding = 'base64'; const secondEncoding = 'hex'; const firstResponse = await got({cache, encoding: firstEncoding}); const secondResponse = await got({cache, encoding: secondEncoding}); const expectedSecondResponseBody = Buffer.from(firstResponse.body, firstEncoding).toString(secondEncoding); t.is(cache.size, 1); t.is(secondResponse.body, expectedSecondResponseBody); }); test('redirects are cached and re-used internally', withServer, async (t, server, got) => { let status301Index = 0; server.get('/301', (_request, response) => { if (status301Index === 0) { response.setHeader('Cache-Control', 'public, max-age=60'); response.setHeader('Location', '/'); response.statusCode = 301; } response.end(); status301Index++; }); let status302Index = 0; server.get('/302', (_request, response) => { if (status302Index === 0) { response.setHeader('Cache-Control', 'public, max-age=60'); response.setHeader('Location', '/'); response.statusCode = 302; } response.end(); status302Index++; }); server.get('/', cacheEndpoint); const cache = new Map(); const A1 = await got('301', {cache}); const B1 = await got('302', {cache}); const A2 = await got('301', {cache}); const B2 = await got('302', {cache}); t.is(cache.size, 3); t.is(A1.body, B1.body); t.is(A1.body, A2.body); t.is(B1.body, B2.body); }); test('cached response has got options', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const options = { username: 'foo', cache }; await got(options); const secondResponse = await got(options); t.is(secondResponse.request.options.username, options.username); }); test('cache error throws `got.CacheError`', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const cache = {}; // @ts-expect-error Error tests await t.throwsAsync(got({cache}), { instanceOf: got.CacheError, code: 'ERR_CACHE_ACCESS' }); }); test('doesn\'t cache response when received HTTP error', withServer, async (t, server, got) => { let isFirstErrorCalled = false; server.get('/', (_request, response) => { if (!isFirstErrorCalled) { response.end('ok'); return; } isFirstErrorCalled = true; response.statusCode = 502; response.end('received 502'); }); const cache = new Map(); const {statusCode, body} = await got({url: '', cache, throwHttpErrors: false}); t.is(statusCode, 200); t.is(body, 'ok'); }); test('DNS cache works', async t => { const instance = got.extend({ dnsCache: true }); await t.notThrowsAsync(instance('https://example.com')); // @ts-expect-error t.is(instance.defaults.options.dnsCache!._cache.size, 1); }); test('DNS cache works - CacheableLookup instance', async t => { const cache = new CacheableLookup(); await t.notThrowsAsync(got('https://example.com', {dnsCache: cache})); t.is((cache as any)._cache.size, 1); }); test('`isFromCache` stream property is undefined before the `response` event', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const stream = got.stream({cache}); t.is(stream.isFromCache, undefined); await getStream(stream); }); test('`isFromCache` stream property is false after the `response` event', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const stream = got.stream({cache}); const response: Response = await pEvent(stream, 'response'); t.is(response.isFromCache, false); t.is(stream.isFromCache, false); await getStream(stream); }); test('`isFromCache` stream property is true if the response was cached', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); await getStream(got.stream({cache})); const stream = got.stream({cache}); const response: Response = await pEvent(stream, 'response'); t.is(response.isFromCache, true); t.is(stream.isFromCache, true); await getStream(stream); }); test('can disable cache by extending the instance', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const instance = got.extend({cache}); await getStream(instance.stream('')); const stream = instance.extend({cache: false}).stream(''); const response: Response = await pEvent(stream, 'response'); t.is(response.isFromCache, false); t.is(stream.isFromCache, false); await getStream(stream); }); test('does not break POST requests', withServer, async (t, server, got) => { server.post('/', async (request, response) => { request.resume(); response.end(JSON.stringify(request.headers)); }); const headers = await got.post('', { body: '', cache: new Map() }).json<{'content-length': string}>(); t.is(headers['content-length'], '0'); }); test('decompresses cached responses', withServer, async (t, server, got) => { const etag = 'foobar'; const payload = JSON.stringify({foo: 'bar'}); const compressed = await promisify(gzip)(payload); server.get('/', (request, response) => { if (request.headers['if-none-match'] === etag) { response.statusCode = 304; response.end(); } else { response.setHeader('content-encoding', 'gzip'); response.setHeader('cache-control', 'public, max-age=60'); response.setHeader('etag', etag); response.end(compressed); } }); const cache = new Map(); for (let i = 0; i < 2; i++) { // eslint-disable-next-line no-await-in-loop await t.notThrowsAsync(got({ cache, responseType: 'json', decompress: true, retry: 2 })); } t.is(cache.size, 1); }); test('can replace the instance\'s HTTP cache', withServer, async (t, server, got) => { server.get('/', cacheEndpoint); const cache = new Map(); const secondCache = new Map(); const instance = got.extend({ mutableDefaults: true, cache }); await t.notThrowsAsync(instance('')); await t.notThrowsAsync(instance('')); instance.defaults.options.cache = secondCache; await t.notThrowsAsync(instance('')); await t.notThrowsAsync(instance('')); t.is(cache.size, 1); t.is(secondCache.size, 1); }); test('does not hang on huge response', withServer, async (t, server, got) => { const bufferSize = 3 * 16 * 1024; const times = 10; const buffer = Buffer.alloc(bufferSize); server.get('/', async (_request, response) => { for (let i = 0; i < 10; i++) { response.write(buffer); // eslint-disable-next-line no-await-in-loop await delay(100); } response.end(); }); const body = await got('', { cache: new Map() }).buffer(); t.is(body.length, bufferSize * times); }); test('cached response ETag', withServer, async (t, server, got) => { const etag = 'foobar'; const body = 'responseBody'; server.get('/', (request, response) => { if (request.headers['if-none-match'] === etag) { response.writeHead(304); response.end(); } else { response.writeHead(200, {ETag: etag}); response.end(body); } }); const cache = new Map(); const originalResponse = await got({cache}); t.false(originalResponse.isFromCache); t.is(originalResponse.body, body); await delay(100); // Added small delay in order to wait the cache to be populated t.is(cache.size, 1); const cachedResponse = await got({cache}); t.true(cachedResponse.isFromCache); t.is(cachedResponse.body, body); }); test('works with http2', async t => { const cache = new Map(); const client = got.extend({ http2: true, cache }); await t.notThrowsAsync(client('https://httpbin.org/anything')); }); test('http-cache-semantics typings', t => { const instance = got.extend({ cacheOptions: { shared: false } }); t.is(instance.defaults.options.cacheOptions.shared, false); }); got-11.8.5/test/cancel.ts000066400000000000000000000161231424347352000151620ustar00rootroot00000000000000import {EventEmitter} from 'events'; import {Readable as ReadableStream} from 'stream'; import stream = require('stream'); import test from 'ava'; import delay = require('delay'); import pEvent = require('p-event'); import getStream = require('get-stream'); import {Handler} from 'express'; import got, {CancelError} from '../source'; import slowDataStream from './helpers/slow-data-stream'; import {GlobalClock} from './helpers/types'; import {ExtendedHttpTestServer} from './helpers/create-http-test-server'; import withServer, {withServerAndFakeTimers} from './helpers/with-server'; const prepareServer = (server: ExtendedHttpTestServer, clock: GlobalClock): {emitter: EventEmitter; promise: Promise} => { const emitter = new EventEmitter(); const promise = new Promise((resolve, reject) => { server.all('/abort', async (request, response) => { emitter.emit('connection'); request.once('aborted', resolve); response.once('finish', reject.bind(null, new Error('Request finished instead of aborting.'))); try { await pEvent(request, 'end'); } catch { // Node.js 15.0.0 throws AND emits `aborted` } response.end(); }); server.get('/redirect', (_request, response) => { response.writeHead(302, { location: `${server.url}/abort` }); response.end(); emitter.emit('sentRedirect'); clock.tick(3000); resolve(); }); }); return {emitter, promise}; }; const downloadHandler = (clock: GlobalClock): Handler => (_request, response) => { response.writeHead(200, { 'transfer-encoding': 'chunked' }); response.flushHeaders(); stream.pipeline( slowDataStream(clock), response, () => { response.end(); } ); }; test.serial('does not retry after cancelation', withServerAndFakeTimers, async (t, server, got, clock) => { const {emitter, promise} = prepareServer(server, clock); const gotPromise = got('redirect', { retry: { calculateDelay: () => { t.fail('Makes a new try after cancelation'); return 0; } } }); emitter.once('sentRedirect', () => { gotPromise.cancel(); }); await t.throwsAsync(gotPromise, { instanceOf: CancelError, code: 'ERR_CANCELED' }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); test.serial('cleans up request timeouts', withServer, async (t, server, got) => { server.get('/', () => {}); const gotPromise = got({ timeout: 10, retry: { calculateDelay: ({computedValue}) => { process.nextTick(() => gotPromise.cancel()); if (computedValue) { return 20; } return 0; }, limit: 1 } }); await t.throwsAsync(gotPromise, { instanceOf: CancelError, code: 'ERR_CANCELED' }); // Wait for unhandled errors await delay(40); }); test.serial('cancels in-progress request', withServerAndFakeTimers, async (t, server, got, clock) => { const {emitter, promise} = prepareServer(server, clock); const body = new ReadableStream({ read() {} }); body.push('1'); const gotPromise = got.post('abort', {body}); // Wait for the connection to be established before canceling emitter.once('connection', () => { gotPromise.cancel(); body.push(null); }); await t.throwsAsync(gotPromise, { instanceOf: CancelError, code: 'ERR_CANCELED' }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); test.serial('cancels in-progress request with timeout', withServerAndFakeTimers, async (t, server, got, clock) => { const {emitter, promise} = prepareServer(server, clock); const body = new ReadableStream({ read() {} }); body.push('1'); const gotPromise = got.post('abort', {body, timeout: 10000}); // Wait for the connection to be established before canceling emitter.once('connection', () => { gotPromise.cancel(); body.push(null); }); await t.throwsAsync(gotPromise, { instanceOf: CancelError, code: 'ERR_CANCELED' }); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); test.serial('cancel immediately', withServerAndFakeTimers, async (t, server, got, clock) => { const promise = new Promise((resolve, reject) => { // We won't get an abort or even a connection // We assume no request within 1000ms equals a (client side) aborted request server.get('/abort', (_request, response) => { response.once('finish', reject.bind(global, new Error('Request finished instead of aborting.'))); response.end(); }); clock.tick(1000); resolve(); }); const gotPromise = got('abort'); gotPromise.cancel(); await t.throwsAsync(gotPromise); await t.notThrowsAsync(promise, 'Request finished instead of aborting.'); }); test('recover from cancelation using cancelable promise attribute', async t => { // Canceled before connection started const p = got('http://example.com'); const recover = p.catch((error: Error) => { if (p.isCanceled) { return; } throw error; }); p.cancel(); await t.notThrowsAsync(recover); }); test('recover from cancellation using error instance', async t => { // Canceled before connection started const p = got('http://example.com'); const recover = p.catch((error: Error) => { if (error instanceof got.CancelError) { return; } throw error; }); p.cancel(); await t.notThrowsAsync(recover); }); test.serial('throws on incomplete (canceled) response - promise', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', downloadHandler(clock)); await t.throwsAsync( got({ timeout: {request: 500}, retry: 0 }), {instanceOf: got.TimeoutError} ); }); test.serial('throws on incomplete (canceled) response - promise #2', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', downloadHandler(clock)); const promise = got('').on('response', () => { clock.tick(500); promise.cancel(); }); await t.throwsAsync(promise, { instanceOf: got.CancelError, code: 'ERR_CANCELED' }); }); test.serial('throws on incomplete (canceled) response - stream', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', downloadHandler(clock)); const errorString = 'Foobar'; const stream = got.stream('').on('response', () => { clock.tick(500); stream.destroy(new Error(errorString)); }); await t.throwsAsync(getStream(stream), {message: errorString}); }); // Note: it will throw, but the response is loaded already. test('throws when canceling cached request', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Cache-Control', 'public, max-age=60'); response.end(Date.now().toString()); }); const cache = new Map(); await got({cache}); const promise = got({cache}).on('response', () => { promise.cancel(); }); await t.throwsAsync(promise, { instanceOf: got.CancelError, code: 'ERR_CANCELED' }); }); test('throws when canceling cached request #2', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Cache-Control', 'public, max-age=60'); response.end(Date.now().toString()); }); const cache = new Map(); await got({cache}); const promise = got({cache}); promise.cancel(); await t.throwsAsync(promise, { instanceOf: got.CancelError, code: 'ERR_CANCELED' }); }); got-11.8.5/test/cookies.ts000066400000000000000000000130721424347352000153710ustar00rootroot00000000000000import net = require('net'); import test from 'ava'; import toughCookie = require('tough-cookie'); import delay = require('delay'); import got from '../source'; import withServer from './helpers/with-server'; test('reads a cookie', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('set-cookie', 'hello=world'); response.end(); }); const cookieJar = new toughCookie.CookieJar(); await got({cookieJar}); const cookie = cookieJar.getCookiesSync(server.url)[0]; t.is(cookie.key, 'hello'); t.is(cookie.value, 'world'); }); test('reads multiple cookies', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('set-cookie', ['hello=world', 'foo=bar']); response.end(); }); const cookieJar = new toughCookie.CookieJar(); await got({cookieJar}); const cookies = cookieJar.getCookiesSync(server.url); const cookieA = cookies[0]; t.is(cookieA.key, 'hello'); t.is(cookieA.value, 'world'); const cookieB = cookies[1]; t.is(cookieB.key, 'foo'); t.is(cookieB.value, 'bar'); }); test('cookies doesn\'t break on redirects', withServer, async (t, server, got) => { server.get('/redirect', (_request, response) => { response.setHeader('set-cookie', ['hello=world', 'foo=bar']); response.setHeader('location', '/'); response.statusCode = 302; response.end(); }); server.get('/', (request, response) => { response.end(request.headers.cookie ?? ''); }); const cookieJar = new toughCookie.CookieJar(); const {body} = await got('redirect', {cookieJar}); t.is(body, 'hello=world; foo=bar'); }); test('throws on invalid cookies', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('set-cookie', 'hello=world; domain=localhost'); response.end(); }); const cookieJar = new toughCookie.CookieJar(); await t.throwsAsync(got({cookieJar}), {message: 'Cookie has domain set to a public suffix'}); }); test('does not throw on invalid cookies when options.ignoreInvalidCookies is set', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('set-cookie', 'hello=world; domain=localhost'); response.end(); }); const cookieJar = new toughCookie.CookieJar(); await got({ cookieJar, ignoreInvalidCookies: true }); const cookies = cookieJar.getCookiesSync(server.url); t.is(cookies.length, 0); }); test('catches store errors', async t => { const error = 'Some error'; const cookieJar = new toughCookie.CookieJar({ findCookies: (_, __, ___, callback) => { callback(new Error(error), []); }, findCookie: () => {}, getAllCookies: () => {}, putCookie: () => {}, removeCookies: () => {}, removeCookie: () => {}, updateCookie: () => {}, synchronous: false }); await t.throwsAsync(got('https://example.com', {cookieJar}), {message: error}); }); test('overrides options.headers.cookie', withServer, async (t, server, got) => { server.get('/redirect', (_request, response) => { response.setHeader('set-cookie', ['hello=world', 'foo=bar']); response.setHeader('location', '/'); response.statusCode = 302; response.end(); }); server.get('/', (request, response) => { response.end(request.headers.cookie ?? ''); }); const cookieJar = new toughCookie.CookieJar(); const {body} = await got('redirect', { cookieJar, headers: { cookie: 'a=b' } }); t.is(body, 'hello=world; foo=bar'); }); test('no unhandled errors', async t => { const server = net.createServer(connection => { connection.end('blah'); }).listen(0); const message = 'snap!'; const options = { cookieJar: { setCookie: async (_rawCookie: string, _url: string) => {}, getCookieString: async (_url: string) => { throw new Error(message); } } }; await t.throwsAsync(got(`http://127.0.0.1:${(server.address() as net.AddressInfo).port}`, options), {message}); await delay(500); t.pass(); server.close(); }); test('accepts custom `cookieJar` object', withServer, async (t, server, got) => { server.get('/', (request, response) => { response.setHeader('set-cookie', ['hello=world']); response.end(request.headers.cookie); }); const cookies: Record = {}; const cookieJar = { async getCookieString(url: string) { t.is(typeof url, 'string'); return cookies[url] || ''; }, async setCookie(rawCookie: string, url: string) { cookies[url] = rawCookie; } }; const first = await got('', {cookieJar}); const second = await got('', {cookieJar}); t.is(first.body, ''); t.is(second.body, 'hello=world'); }); test('throws on invalid `options.cookieJar.setCookie`', async t => { await t.throwsAsync(got('https://example.com', { cookieJar: { // @ts-expect-error Error tests setCookie: 123 } }), {message: 'Expected value which is `Function`, received value of type `number`.'}); }); test('throws on invalid `options.cookieJar.getCookieString`', async t => { await t.throwsAsync(got('https://example.com', { cookieJar: { setCookie: async () => {}, // @ts-expect-error Error tests getCookieString: 123 } }), {message: 'Expected value which is `Function`, received value of type `number`.'}); }); test('cookies are cleared when redirecting to a different hostname (no cookieJar)', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: 'https://httpbin.org/anything' }); response.end(); }); const {headers} = await got('', { headers: { cookie: 'foo=bar', 'user-agent': 'custom' } }).json(); t.is(headers.Cookie, undefined); t.is(headers['User-Agent'], 'custom'); }); got-11.8.5/test/create.ts000066400000000000000000000204711424347352000152010ustar00rootroot00000000000000import {Agent as HttpAgent, IncomingMessage, request as httpRequest, RequestOptions} from 'http'; import {URL} from 'url'; import test from 'ava'; import is from '@sindresorhus/is'; import {Handler} from 'express'; import got, { BeforeRequestHook, Headers, Hooks, RequestFunction } from '../source'; import withServer from './helpers/with-server'; const echoHeaders: Handler = (request, response) => { request.resume(); response.end(JSON.stringify(request.headers)); }; test('preserves global defaults', withServer, async (t, server, got) => { server.get('/', echoHeaders); const globalHeaders = await got('').json(); const instanceHeaders = await got.extend()('').json(); t.deepEqual(instanceHeaders, globalHeaders); }); test('supports instance defaults', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instance = got.extend({ headers: { 'user-agent': 'custom-ua-string' } }); const headers = await instance('').json(); t.is(headers['user-agent'], 'custom-ua-string'); }); test('supports invocation overrides', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instance = got.extend({ headers: { 'user-agent': 'custom-ua-string' } }); const headers = await instance({ headers: { 'user-agent': 'different-ua-string' } }).json(); t.is(headers['user-agent'], 'different-ua-string'); }); test('carries previous instance defaults', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instanceA = got.extend({ headers: { 'x-foo': 'foo' } }); const instanceB = instanceA.extend({ headers: { 'x-bar': 'bar' } }); const headers = await instanceB('').json(); t.is(headers['x-foo'], 'foo'); t.is(headers['x-bar'], 'bar'); }); test('custom headers (extend)', withServer, async (t, server, got) => { server.get('/', echoHeaders); const options = {headers: {unicorn: 'rainbow'}}; const instance = got.extend(options); const headers = await instance('').json(); t.is(headers.unicorn, 'rainbow'); }); test('extend overwrites arrays with a deep clone', t => { const beforeRequest = [0]; const a = got.extend({hooks: {beforeRequest} as unknown as Hooks}); beforeRequest[0] = 1; t.deepEqual(a.defaults.options.hooks.beforeRequest, [0] as unknown as BeforeRequestHook[]); t.not(a.defaults.options.hooks.beforeRequest, beforeRequest as unknown as BeforeRequestHook[]); }); test('extend keeps the old value if the new one is undefined', t => { const a = got.extend({headers: undefined}); t.deepEqual( a.defaults.options.headers, got.defaults.options.headers ); }); test('hooks are merged on got.extend()', t => { const hooksA = [() => {}]; const hooksB = [() => {}]; const instanceA = got.extend({hooks: {beforeRequest: hooksA}}); const extended = instanceA.extend({hooks: {beforeRequest: hooksB}}); t.deepEqual(extended.defaults.options.hooks.beforeRequest, hooksA.concat(hooksB)); }); test('custom endpoint with custom headers (extend)', withServer, async (t, server) => { server.all('/', echoHeaders); const instance = got.extend({headers: {unicorn: 'rainbow'}, prefixUrl: server.url}); const headers = await instance('').json(); t.is(headers.unicorn, 'rainbow'); t.not(headers['user-agent'], undefined); }); test('no tampering with defaults', t => { t.throws(() => { got.defaults.options.prefixUrl = 'http://google.com'; }); t.is(got.defaults.options.prefixUrl, ''); }); test('can set defaults to `got.mergeOptions(...)`', t => { const instance = got.extend({ mutableDefaults: true, followRedirect: false }); t.notThrows(() => { instance.defaults.options = got.mergeOptions(instance.defaults.options, { followRedirect: true }); }); t.true(instance.defaults.options.followRedirect); t.notThrows(() => { instance.defaults.options = got.mergeOptions({}); }); t.is(instance.defaults.options.followRedirect, undefined); }); test('can set mutable defaults using got.extend', t => { const instance = got.extend({ mutableDefaults: true, followRedirect: false }); t.notThrows(() => { instance.defaults.options.followRedirect = true; }); t.true(instance.defaults.options.followRedirect); }); test('only plain objects are freezed', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instance = got.extend({ agent: { http: new HttpAgent({keepAlive: true}) }, mutableDefaults: true }); t.notThrows(() => { (instance.defaults.options.agent as any).http.keepAlive = true; }); }); test('defaults are cloned on instance creation', t => { const options = {foo: 'bar', hooks: {beforeRequest: [() => {}]}}; const instance = got.extend(options); t.notThrows(() => { options.foo = 'foo'; delete options.hooks.beforeRequest[0]; }); // @ts-expect-error This IS correct t.not(options.foo, instance.defaults.options.foo); t.not(options.hooks.beforeRequest, instance.defaults.options.hooks.beforeRequest); }); test('ability to pass a custom request method', withServer, async (t, server, got) => { server.get('/', echoHeaders); let isCalled = false; const request: RequestFunction = (...args: [ string | URL | RequestOptions, (RequestOptions | ((response: IncomingMessage) => void))?, ((response: IncomingMessage) => void)? ]) => { isCalled = true; // @ts-expect-error Overload error return httpRequest(...args); }; const instance = got.extend({request}); await instance(''); t.true(isCalled); }); test('does not include the `request` option in normalized `http` options', withServer, async (t, server, got) => { server.get('/', echoHeaders); let isCalled = false; const request: RequestFunction = (...args: [ string | URL | RequestOptions, (RequestOptions | ((response: IncomingMessage) => void))?, ((response: IncomingMessage) => void)? ]) => { isCalled = true; t.false(Reflect.has(args[0] as RequestOptions, 'request')); // @ts-expect-error Overload error return httpRequest(...args); }; const instance = got.extend({request}); await instance(''); t.true(isCalled); }); test('should pass an options object into an initialization hook after .extend', withServer, async (t, server, got) => { t.plan(1); server.get('/', echoHeaders); const instance = got.extend({ hooks: { init: [ options => { t.deepEqual(options, {}); } ] } }); await instance(''); }); test('hooks aren\'t overriden when merging options', withServer, async (t, server, got) => { server.get('/', echoHeaders); let isCalled = false; const instance = got.extend({ hooks: { beforeRequest: [ () => { isCalled = true; } ] } }); await instance({}); t.true(isCalled); }); test('extend with custom handlers', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instance = got.extend({ handlers: [ (options, next) => { options.headers.unicorn = 'rainbow'; return next(options); } ] }); const headers = await instance('').json(); t.is(headers.unicorn, 'rainbow'); }); test('extend with instances', t => { const a = got.extend({prefixUrl: new URL('https://example.com/')}); const b = got.extend(a); t.is(b.defaults.options.prefixUrl.toString(), 'https://example.com/'); }); test('extend with a chain', t => { const a = got.extend({prefixUrl: 'https://example.com/'}); const b = got.extend(a, {headers: {foo: 'bar'}}); t.is(b.defaults.options.prefixUrl.toString(), 'https://example.com/'); t.is(b.defaults.options.headers.foo, 'bar'); }); test('async handlers', withServer, async (t, server, got) => { server.get('/', echoHeaders); const instance = got.extend({ handlers: [ async (options, next) => { const result = await next(options); // @ts-expect-error Manual tests result.modified = true; return result; } ] }); const promise = instance(''); t.true(is.function_(promise.cancel)); // @ts-expect-error Manual tests t.true((await promise).modified); }); test('async handlers can throw', async t => { const message = 'meh'; const instance = got.extend({ handlers: [ async () => { throw new Error(message); } ] }); await t.throwsAsync(instance('https://example.com'), {message}); }); test('setting dnsCache to true points to global cache', t => { const a = got.extend({ dnsCache: true }); const b = got.extend({ dnsCache: true }); t.is(a.defaults.options.dnsCache, b.defaults.options.dnsCache); }); got-11.8.5/test/error.ts000066400000000000000000000205541424347352000150710ustar00rootroot00000000000000import {promisify} from 'util'; import net = require('net'); import http = require('http'); import stream = require('stream'); import test from 'ava'; import getStream = require('get-stream'); import is from '@sindresorhus/is'; import got, {RequestError, HTTPError, TimeoutError} from '../source'; import withServer from './helpers/with-server'; const pStreamPipeline = promisify(stream.pipeline); test('properties', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end('not'); }); const url = new URL(server.url); const error = await t.throwsAsync(got('')); t.truthy(error); t.truthy(error.response); t.truthy(error.options); t.false({}.propertyIsEnumerable.call(error, 'options')); t.false({}.propertyIsEnumerable.call(error, 'response')); // This fails because of TS 3.7.2 useDefineForClassFields // Class fields will always be initialized, even though they are undefined // A test to check for undefined is in place below // t.false({}.hasOwnProperty.call(error, 'code')); t.is(error.code, 'ERR_NON_2XX_3XX_RESPONSE'); t.is(error.message, 'Response code 404 (Not Found)'); t.deepEqual(error.options.url, url); t.is(error.response.headers.connection, 'close'); t.is(error.response.body, 'not'); }); test('catches dns errors', async t => { const error = await t.throwsAsync(got('http://doesntexist', {retry: 0})); t.truthy(error); t.regex(error.message, /ENOTFOUND|EAI_AGAIN/); t.is(error.options.url.host, 'doesntexist'); t.is(error.options.method, 'GET'); t.is(error.code, 'ENOTFOUND'); }); test('`options.body` form error message', async t => { // @ts-expect-error Error tests await t.throwsAsync(got.post('https://example.com', {body: Buffer.from('test'), form: ''}), { message: 'The `body`, `json` and `form` options are mutually exclusive' }); }); test('no plain object restriction on json body', withServer, async (t, server, got) => { server.post('/body', async (request, response) => { await pStreamPipeline(request, response); }); class CustomObject { a = 123; } const body = await got.post('body', {json: new CustomObject()}).json(); t.deepEqual(body, {a: 123}); }); test('default status message', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 400; response.end('body'); }); const error = await t.throwsAsync(got('')); t.is(error.response.statusCode, 400); t.is(error.response.statusMessage, 'Bad Request'); }); test('custom status message', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 400; response.statusMessage = 'Something Exploded'; response.end('body'); }); const error = await t.throwsAsync(got('')); t.is(error.response.statusCode, 400); t.is(error.response.statusMessage, 'Something Exploded'); }); test('custom body', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end('not'); }); const error = await t.throwsAsync(got('')); t.is(error.response.statusCode, 404); t.is(error.response.body, 'not'); }); test('contains Got options', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end(); }); const options: {agent: false} = { agent: false }; const error = await t.throwsAsync(got(options)); t.is(error.options.agent, options.agent); }); test('empty status message is overriden by the default one', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(400, ''); response.end('body'); }); const error = await t.throwsAsync(got('')); t.is(error.response.statusCode, 400); t.is(error.response.statusMessage, http.STATUS_CODES[400]); }); test('`http.request` error', async t => { await t.throwsAsync(got('https://example.com', { request: () => { throw new TypeError('The header content contains invalid characters'); } }), { instanceOf: got.RequestError, code: 'ERR_GOT_REQUEST_ERROR', message: 'The header content contains invalid characters' }); }); test('`http.request` pipe error', async t => { const message = 'snap!'; await t.throwsAsync(got('https://example.com', { // @ts-expect-error Error tests request: () => { const proxy = new stream.PassThrough(); const anyProxy = proxy as any; anyProxy.socket = { remoteAddress: '', prependOnceListener: () => {} }; anyProxy.headers = {}; anyProxy.abort = () => {}; proxy.resume(); proxy.read = () => { proxy.destroy(new Error(message)); return null; }; return proxy; }, throwHttpErrors: false }), { instanceOf: got.RequestError, message }); }); test('`http.request` error through CacheableRequest', async t => { await t.throwsAsync(got('https://example.com', { request: () => { throw new TypeError('The header content contains invalid characters'); }, cache: new Map() }), { instanceOf: got.RequestError, message: 'The header content contains invalid characters' }); }); test('normalization errors using convenience methods', async t => { const url = 'undefined/https://example.com'; await t.throwsAsync(got(url).json().text().buffer(), {message: `Invalid URL: ${url}`}); }); test('errors can have request property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end(); }); const error = await t.throwsAsync(got('')); t.truthy(error.response); t.truthy(error.request.downloadProgress); }); test('promise does not hang on timeout on HTTP error', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.write('asdf'); }); await t.throwsAsync(got({ timeout: 100 }), { instanceOf: TimeoutError, code: 'ETIMEDOUT' }); }); test('no uncaught parse errors', async t => { const server = net.createServer(); const listen = promisify(server.listen.bind(server)); const close = promisify(server.close.bind(server)); // @ts-expect-error TS is sooo dumb. It doesn't need an argument at all. await listen(); server.on('connection', socket => { socket.resume(); socket.end([ 'HTTP/1.1 404 Not Found', 'transfer-encoding: chunked', '', '0', '', '' ].join('\r\n')); }); await t.throwsAsync(got.head(`http://localhost:${(server.address() as net.AddressInfo).port}`), { message: /^Parse Error/ }); await close(); }); // Fails randomly on Node 10: // Blocked by https://github.com/istanbuljs/nyc/issues/619 // eslint-disable-next-line ava/no-skip-test test.skip('the old stacktrace is recovered', async t => { const error = await t.throwsAsync(got('https://example.com', { request: () => { throw new Error('foobar'); } })); t.true(error.stack!.includes('at Object.request')); // The first `at get` points to where the error was wrapped, // the second `at get` points to the real cause. t.not(error.stack!.indexOf('at get'), error.stack!.lastIndexOf('at get')); }); test.serial('custom stack trace', withServer, async (t, _server, got) => { const ErrorCaptureStackTrace = Error.captureStackTrace; const enable = () => { Error.captureStackTrace = (target: {stack: any}) => { target.stack = [ 'line 1', 'line 2' ]; }; }; const disable = () => { Error.captureStackTrace = ErrorCaptureStackTrace; }; // Node.js default behavior { const stream = got.stream(''); stream.destroy(new Error('oh no')); const caught = await t.throwsAsync(getStream(stream)); t.is(is(caught.stack), 'string'); } // Passing a custom error { enable(); const error = new Error('oh no'); disable(); const stream = got.stream(''); stream.destroy(error); const caught = await t.throwsAsync(getStream(stream)); t.is(is(caught.stack), 'string'); } // Custom global behavior { enable(); const error = new Error('oh no'); const stream = got.stream(''); stream.destroy(error); const caught = await t.throwsAsync(getStream(stream)); t.is(is(caught.stack), 'Array'); disable(); } // Passing a default error that needs some processing { const error = new Error('oh no'); enable(); const stream = got.stream(''); stream.destroy(error); const caught = await t.throwsAsync(getStream(stream)); t.is(is(caught.stack), 'Array'); disable(); } }); got-11.8.5/test/fixtures/000077500000000000000000000000001424347352000152335ustar00rootroot00000000000000got-11.8.5/test/fixtures/ok000066400000000000000000000000031424347352000155600ustar00rootroot00000000000000ok got-11.8.5/test/fixtures/stream-content-length000066400000000000000000000000111424347352000213700ustar00rootroot00000000000000Unicorns got-11.8.5/test/gzip.ts000066400000000000000000000075751424347352000147210ustar00rootroot00000000000000import {promisify} from 'util'; import zlib = require('zlib'); import test from 'ava'; import getStream = require('get-stream'); import withServer from './helpers/with-server'; import {HTTPError, ReadError} from '../source'; const testContent = 'Compressible response content.\n'; const testContentUncompressed = 'Uncompressed response content.\n'; let gzipData: Buffer; test.before('setup', async () => { gzipData = await promisify(zlib.gzip)(testContent); }); test('decompress content', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData); }); t.is((await got('')).body, testContent); }); test('decompress content on error', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.status(404); response.end(gzipData); }); const error = await t.throwsAsync(got('')); t.is(error.response.body, testContent); }); test('decompress content - stream', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData); }); t.is((await getStream(got.stream(''))), testContent); }); test('handles gzip error', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end('Not gzipped content'); }); await t.throwsAsync(got(''), { name: 'ReadError', message: 'incorrect header check' }); }); test('no unhandled `Premature close` error', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.write('Not gzipped content'); }); await t.throwsAsync(got(''), { name: 'ReadError', message: 'incorrect header check' }); }); test('handles gzip error - stream', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end('Not gzipped content'); }); await t.throwsAsync(getStream(got.stream('')), { name: 'ReadError', message: 'incorrect header check' }); }); test('decompress option opts out of decompressing', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData); }); const {body} = await got({decompress: false, responseType: 'buffer'}); t.is(Buffer.compare(body, gzipData), 0); }); test('decompress option doesn\'t alter encoding of uncompressed responses', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end(testContentUncompressed); }); const {body} = await got({decompress: false}); t.is(body, testContentUncompressed); }); test('preserves `headers` property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData); }); t.truthy((await got('')).headers); }); test('does not break HEAD responses', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end(); }); t.is((await got.head('')).body, ''); }); test('does not ignore missing data', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData.slice(0, -1)); }); await t.throwsAsync(got(''), { instanceOf: ReadError, message: 'unexpected end of file' }); }); test('response has `url` and `requestUrl` properties', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('Content-Encoding', 'gzip'); response.end(gzipData); }); const response = await got(''); t.truthy(response.url); t.truthy(response.requestUrl); }); got-11.8.5/test/headers.ts000066400000000000000000000164141424347352000153530ustar00rootroot00000000000000import fs = require('fs'); import {promisify} from 'util'; import path = require('path'); import test from 'ava'; import {Handler} from 'express'; import FormData = require('form-data'); import got, {Headers} from '../source'; import withServer from './helpers/with-server'; const supportsBrotli = typeof (process.versions as any).brotli === 'string'; const echoHeaders: Handler = (request, response) => { request.resume(); response.end(JSON.stringify(request.headers)); }; test('`user-agent`', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = await got('').json(); t.is(headers['user-agent'], 'got (https://github.com/sindresorhus/got)'); }); test('`accept-encoding`', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = await got('').json(); t.is(headers['accept-encoding'], supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate'); }); test('does not override provided `accept-encoding`', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = await got({ headers: { 'accept-encoding': 'gzip' } }).json(); t.is(headers['accept-encoding'], 'gzip'); }); test('does not remove user headers from `url` object argument', withServer, async (t, server) => { server.get('/', echoHeaders); const headers = (await got({ url: `http://${server.hostname}:${server.port}`, responseType: 'json', headers: { 'X-Request-Id': 'value' } })).body; t.is(headers.accept, 'application/json'); t.is(headers['user-agent'], 'got (https://github.com/sindresorhus/got)'); t.is(headers['accept-encoding'], supportsBrotli ? 'gzip, deflate, br' : 'gzip, deflate'); t.is(headers['x-request-id'], 'value'); }); test('does not set `accept-encoding` header when `options.decompress` is false', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = await got({ decompress: false }).json(); // @ts-expect-error Error tests t.false(Reflect.has(headers, 'accept-encoding')); }); test('`accept` header with `json` option', withServer, async (t, server, got) => { server.get('/', echoHeaders); let headers = await got('').json(); t.is(headers.accept, 'application/json'); headers = await got({ headers: { accept: '' } }).json(); t.is(headers.accept, ''); }); test('`host` header', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = await got('').json(); t.is(headers.host, `localhost:${server.port}`); }); test('transforms names to lowercase', withServer, async (t, server, got) => { server.get('/', echoHeaders); const headers = (await got({ headers: { 'ACCEPT-ENCODING': 'identity' }, responseType: 'json' })).body; t.is(headers['accept-encoding'], 'identity'); }); test('setting `content-length` to 0', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ headers: { 'content-length': '0' }, body: 'sup' }); const headers = JSON.parse(body); t.is(headers['content-length'], '0'); }); test('sets `content-length` to `0` when requesting PUT with empty body', withServer, async (t, server, got) => { server.put('/', echoHeaders); const {body} = await got({ method: 'PUT' }); const headers = JSON.parse(body); t.is(headers['content-length'], '0'); }); test('form manual `content-type` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ headers: { 'content-type': 'custom' }, form: { a: 1 } }); const headers = JSON.parse(body); t.is(headers['content-type'], 'custom'); }); test('form-data manual `content-type` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const form = new FormData(); form.append('a', 'b'); const {body} = await got.post({ headers: { 'content-type': 'custom' }, body: form }); const headers = JSON.parse(body); t.is(headers['content-type'], 'custom'); }); test('form-data automatic `content-type` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const form = new FormData(); form.append('a', 'b'); const {body} = await got.post({ body: form }); const headers = JSON.parse(body); t.is(headers['content-type'], `multipart/form-data; boundary=${form.getBoundary()}`); }); test('form-data sets `content-length` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const form = new FormData(); form.append('a', 'b'); const {body} = await got.post({body: form}); const headers = JSON.parse(body); t.is(headers['content-length'], '157'); }); test('stream as `options.body` sets `content-length` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const fixture = path.resolve('test/fixtures/stream-content-length'); const {size} = await promisify(fs.stat)(fixture); const {body} = await got.post({ body: fs.createReadStream(fixture) }); const headers = JSON.parse(body); t.is(Number(headers['content-length']), size); }); test('buffer as `options.body` sets `content-length` header', withServer, async (t, server, got) => { server.post('/', echoHeaders); const buffer = Buffer.from('unicorn'); const {body} = await got.post({ body: buffer }); const headers = JSON.parse(body); t.is(Number(headers['content-length']), buffer.length); }); test('throws on null value headers', async t => { await t.throwsAsync(got({ url: 'https://example.com', headers: { // @ts-expect-error Testing purposes 'user-agent': null } }), { message: 'Use `undefined` instead of `null` to delete the `user-agent` header' }); }); test('removes undefined value headers', withServer, async (t, server, got) => { server.get('/', echoHeaders); const {body} = await got({ headers: { 'user-agent': undefined } }); const headers = JSON.parse(body); t.is(headers['user-agent'], undefined); }); test('non-existent headers set to undefined are omitted', withServer, async (t, server, got) => { server.get('/', echoHeaders); const {body} = await got({ headers: { blah: undefined } }); const headers = JSON.parse(body); t.false(Reflect.has(headers, 'blah')); }); test('preserve port in host header if non-standard port', withServer, async (t, server, got) => { server.get('/', echoHeaders); const body = await got('').json(); t.is(body.host, `localhost:${server.port}`); }); test('strip port in host header if explicit standard port (:80) & protocol (HTTP)', async t => { const body = await got('http://httpbin.org:80/headers').json<{headers: Headers}>(); t.is(body.headers.Host, 'httpbin.org'); }); test('strip port in host header if explicit standard port (:443) & protocol (HTTPS)', async t => { const body = await got('https://httpbin.org:443/headers').json<{headers: Headers}>(); t.is(body.headers.Host, 'httpbin.org'); }); test('strip port in host header if implicit standard port & protocol (HTTP)', async t => { const body = await got('http://httpbin.org/headers').json<{headers: Headers}>(); t.is(body.headers.Host, 'httpbin.org'); }); test('strip port in host header if implicit standard port & protocol (HTTPS)', async t => { const body = await got('https://httpbin.org/headers').json<{headers: Headers}>(); t.is(body.headers.Host, 'httpbin.org'); }); got-11.8.5/test/helpers.ts000066400000000000000000000011601424347352000153720ustar00rootroot00000000000000import test from 'ava'; import got, {HTTPError} from '../source'; import withServer from './helpers/with-server'; test('works', withServer, async (t, server) => { server.get('/', (_request, response) => { response.end('ok'); }); server.get('/404', (_request, response) => { response.statusCode = 404; response.end('not found'); }); t.is((await got.get(server.url)).body, 'ok'); const error = await t.throwsAsync(got.get(`${server.url}/404`), {instanceOf: HTTPError}); t.is(error.response.body, 'not found'); await t.throwsAsync(got.get('.com', {retry: 0}), {message: 'Invalid URL: .com'}); }); got-11.8.5/test/helpers/000077500000000000000000000000001424347352000150245ustar00rootroot00000000000000got-11.8.5/test/helpers/create-http-test-server.ts000066400000000000000000000026521424347352000221020ustar00rootroot00000000000000import http = require('http'); import net = require('net'); import express = require('express'); import pify = require('pify'); import bodyParser = require('body-parser'); export type HttpServerOptions = { bodyParser?: express.NextFunction | false; }; export interface ExtendedHttpTestServer extends express.Express { http: http.Server; url: string; port: number; hostname: string; close: () => Promise; } const createHttpTestServer = async (options: HttpServerOptions = {}): Promise => { const server = express() as ExtendedHttpTestServer; server.http = http.createServer(server); server.set('etag', false); if (options.bodyParser !== false) { server.use(bodyParser.json({limit: '1mb', type: 'application/json', ...options.bodyParser})); server.use(bodyParser.text({limit: '1mb', type: 'text/plain', ...options.bodyParser})); server.use(bodyParser.urlencoded({limit: '1mb', type: 'application/x-www-form-urlencoded', extended: true, ...options.bodyParser})); server.use(bodyParser.raw({limit: '1mb', type: 'application/octet-stream', ...options.bodyParser})); } await pify(server.http.listen.bind(server.http))(); server.port = (server.http.address() as net.AddressInfo).port; server.url = `http://localhost:${(server.port)}`; server.hostname = 'localhost'; server.close = async () => pify(server.http.close.bind(server.http))(); return server; }; export default createHttpTestServer; got-11.8.5/test/helpers/create-https-test-server.ts000066400000000000000000000035741424347352000222710ustar00rootroot00000000000000import https = require('https'); import net = require('net'); import express = require('express'); import pify = require('pify'); import pem = require('pem'); export type HttpsServerOptions = { commonName?: string; days?: number; }; export interface ExtendedHttpsTestServer extends express.Express { https: https.Server; caKey: Buffer; caCert: Buffer; url: string; port: number; close: () => Promise; } const createHttpsTestServer = async (options: HttpsServerOptions = {}): Promise => { const createCSR = pify(pem.createCSR); const createCertificate = pify(pem.createCertificate); const caCSRResult = await createCSR({commonName: 'authority'}); const caResult = await createCertificate({ csr: caCSRResult.csr, clientKey: caCSRResult.clientKey, selfSigned: true }); const caKey = caResult.clientKey; const caCert = caResult.certificate; const serverCSRResult = await createCSR({commonName: options.commonName ?? 'localhost'}); const serverResult = await createCertificate({ csr: serverCSRResult.csr, clientKey: serverCSRResult.clientKey, serviceKey: caKey, serviceCertificate: caCert, days: options.days ?? 365 }); const serverKey = serverResult.clientKey; const serverCert = serverResult.certificate; const server = express() as ExtendedHttpsTestServer; server.https = https.createServer( { key: serverKey, cert: serverCert, ca: caCert, requestCert: true, rejectUnauthorized: false // This should be checked by the test }, server ); server.set('etag', false); await pify(server.https.listen.bind(server.https))(); server.caKey = caKey; server.caCert = caCert; server.port = (server.https.address() as net.AddressInfo).port; server.url = `https://localhost:${(server.port)}`; server.close = async () => pify(server.https.close.bind(server.https))(); return server; }; export default createHttpsTestServer; got-11.8.5/test/helpers/slow-data-stream.ts000066400000000000000000000004511424347352000205600ustar00rootroot00000000000000import {Readable} from 'stream'; import {Clock} from '@sinonjs/fake-timers'; export default (clock: Clock): Readable => { let i = 0; return new Readable({ read() { if (i++ < 10) { this.push('data\n'.repeat(100)); clock.tick(100); } else { this.push(null); } } }); }; got-11.8.5/test/helpers/types.ts000066400000000000000000000006701424347352000165430ustar00rootroot00000000000000import {Server} from 'http'; import {TestServer} from 'create-test-server'; import * as FakeTimers from '@sinonjs/fake-timers'; export interface ExtendedHttpServer extends Server { socketPath: string; } export interface ExtendedTestServer extends TestServer { hostname: string; sslHostname: string; } export type InstalledClock = ReturnType; export type GlobalClock = InstalledClock | FakeTimers.NodeClock; got-11.8.5/test/helpers/with-server.ts000066400000000000000000000073421424347352000176610ustar00rootroot00000000000000import {promisify} from 'util'; import * as test from 'ava'; import is from '@sindresorhus/is'; import http = require('http'); import tempy = require('tempy'); import createHttpsTestServer, {ExtendedHttpsTestServer, HttpsServerOptions} from './create-https-test-server'; import createHttpTestServer, {ExtendedHttpTestServer, HttpServerOptions} from './create-http-test-server'; import FakeTimers = require('@sinonjs/fake-timers'); import got, {InstanceDefaults, Got} from '../../source'; import {ExtendedHttpServer, GlobalClock, InstalledClock} from './types'; export type RunTestWithServer = (t: test.ExecutionContext, server: ExtendedHttpTestServer, got: Got, clock: GlobalClock) => Promise | void; export type RunTestWithHttpsServer = (t: test.ExecutionContext, server: ExtendedHttpsTestServer, got: Got, fakeTimer?: GlobalClock) => Promise | void; export type RunTestWithSocket = (t: test.ExecutionContext, server: ExtendedHttpServer) => Promise | void; const generateHook = ({install, options: testServerOptions}: {install?: boolean; options?: HttpServerOptions}): test.Macro<[RunTestWithServer]> => async (t, run) => { const clock = install ? FakeTimers.install() : FakeTimers.createClock() as GlobalClock; // Re-enable body parsing to investigate https://github.com/sindresorhus/got/issues/1186 const server = await createHttpTestServer(is.plainObject(testServerOptions) ? testServerOptions : { bodyParser: { type: () => false } as any }); const options: InstanceDefaults = { // @ts-expect-error Augmenting for test detection avaTest: t.title, handlers: [ (options, next) => { const result = next(options); clock.tick(0); // @ts-expect-error FIXME: Incompatible union type signatures result.on('response', () => { clock.tick(0); }); return result; } ] }; const preparedGot = got.extend({prefixUrl: server.url, ...options}); try { await run(t, server, preparedGot, clock); } finally { await server.close(); } if (install) { (clock as InstalledClock).uninstall(); } }; export const withBodyParsingServer = generateHook({install: false, options: {}}); export default generateHook({install: false}); export const withServerAndFakeTimers = generateHook({install: true}); const generateHttpsHook = (options?: HttpsServerOptions, installFakeTimer = false): test.Macro<[RunTestWithHttpsServer]> => async (t, run) => { const fakeTimer = installFakeTimer ? FakeTimers.install() as GlobalClock : undefined; const server = await createHttpsTestServer(options); const preparedGot = got.extend({ // @ts-expect-error Augmenting for test detection avaTest: t.title, handlers: [ (options, next) => { const result = next(options); fakeTimer?.tick(0); // @ts-expect-error FIXME: Incompatible union type signatures result.on('response', () => { fakeTimer?.tick(0); }); return result; } ], prefixUrl: server.url, https: { certificateAuthority: (server as any).caCert, rejectUnauthorized: true } }); try { await run(t, server, preparedGot, fakeTimer); } finally { await server.close(); } if (installFakeTimer) { (fakeTimer as InstalledClock).uninstall(); } }; export const withHttpsServer = generateHttpsHook; // TODO: remove this when `create-test-server` supports custom listen export const withSocketServer: test.Macro<[RunTestWithSocket]> = async (t, run) => { const socketPath = tempy.file({extension: 'socket'}); const server = http.createServer((request, response) => { server.emit(request.url!, request, response); }) as ExtendedHttpServer; server.socketPath = socketPath; await promisify(server.listen.bind(server))(socketPath); try { await run(t, server); } finally { await promisify(server.close.bind(server))(); } }; got-11.8.5/test/hooks.ts000066400000000000000000000611201424347352000150550ustar00rootroot00000000000000import {URL} from 'url'; import {Agent as HttpAgent} from 'http'; import test, {Constructor} from 'ava'; import nock = require('nock'); import getStream = require('get-stream'); import FormData = require('form-data'); import sinon = require('sinon'); import delay = require('delay'); import {Handler} from 'express'; import Responselike = require('responselike'); import got, {RequestError, HTTPError, Response} from '../source'; import withServer from './helpers/with-server'; const errorString = 'oops'; const error = new Error(errorString); const echoHeaders: Handler = (request, response) => { response.end(JSON.stringify(request.headers)); }; const echoBody: Handler = async (request, response) => { response.end(await getStream(request)); }; const echoUrl: Handler = (request, response) => { response.end(request.url); }; const retryEndpoint: Handler = (request, response) => { if (request.headers.foo) { response.statusCode = 302; response.setHeader('location', '/'); response.end(); } response.statusCode = 500; response.end(); }; const redirectEndpoint: Handler = (_request, response) => { response.statusCode = 302; response.setHeader('location', '/'); response.end(); }; const createAgentSpy = (AgentClass: Constructor): {agent: T; spy: sinon.SinonSpy} => { const agent: T = new AgentClass({keepAlive: true}); // @ts-expect-error This IS correct const spy = sinon.spy(agent, 'addRequest'); return {agent, spy}; }; test('async hooks', withServer, async (t, server, got) => { server.get('/', echoHeaders); const {body} = await got>({ responseType: 'json', hooks: { beforeRequest: [ async options => { await delay(100); options.headers.foo = 'bar'; } ] } }); t.is(body.foo, 'bar'); }); test('catches init thrown errors', async t => { await t.throwsAsync(got('https://example.com', { hooks: { init: [() => { throw error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('passes init thrown errors to beforeError hooks (promise-only)', async t => { t.plan(2); await t.throwsAsync(got('https://example.com', { hooks: { init: [() => { throw error; }], beforeError: [error => { t.is(error.message, errorString); return error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('passes init thrown errors to beforeError hooks (promise-only) - beforeError rejection', async t => { const message = 'foo, bar!'; await t.throwsAsync(got('https://example.com', { hooks: { init: [() => { throw error; }], beforeError: [() => { throw new Error(message); }] } }), {message}); }); test('catches beforeRequest thrown errors', async t => { await t.throwsAsync(got('https://example.com', { hooks: { beforeRequest: [() => { throw error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('catches beforeRedirect thrown errors', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/redirect', redirectEndpoint); await t.throwsAsync(got('redirect', { hooks: { beforeRedirect: [() => { throw error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('catches beforeRetry thrown errors', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/retry', retryEndpoint); await t.throwsAsync(got('retry', { hooks: { beforeRetry: [() => { throw error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('catches afterResponse thrown errors', withServer, async (t, server, got) => { server.get('/', echoHeaders); await t.throwsAsync(got({ hooks: { afterResponse: [() => { throw error; }] } }), { instanceOf: RequestError, message: errorString }); }); test('accepts an async function as init hook', async t => { await got('https://example.com', { hooks: { init: [ async () => { t.pass(); } ] } }); }); test('catches beforeRequest promise rejections', async t => { await t.throwsAsync(got('https://example.com', { hooks: { beforeRequest: [ async () => { throw error; } ] } }), { instanceOf: RequestError, message: errorString }); }); test('catches beforeRedirect promise rejections', withServer, async (t, server, got) => { server.get('/', redirectEndpoint); await t.throwsAsync(got({ hooks: { beforeRedirect: [ async () => { throw error; } ] } }), { instanceOf: RequestError, message: errorString }); }); test('catches beforeRetry promise rejections', withServer, async (t, server, got) => { server.get('/retry', retryEndpoint); await t.throwsAsync(got('retry', { hooks: { beforeRetry: [ async () => { throw error; } ] } }), { instanceOf: RequestError, message: errorString }); }); test('catches afterResponse promise rejections', withServer, async (t, server, got) => { server.get('/', echoHeaders); await t.throwsAsync(got({ hooks: { afterResponse: [ async () => { throw error; } ] } }), {message: errorString}); }); test('catches beforeError errors', async t => { await t.throwsAsync(got('https://example.com', { request: () => { throw new Error('No way'); }, hooks: { beforeError: [ async () => { throw error; } ] } }), {message: errorString}); }); test('init is called with options', withServer, async (t, server, got) => { server.get('/', echoHeaders); const context = {}; await got({ hooks: { init: [ options => { t.is(options.context, context); } ] }, context }); }); test('init from defaults is called with options', withServer, async (t, server, got) => { server.get('/', echoHeaders); const context = {}; const instance = got.extend({ hooks: { init: [ options => { t.is(options.context, context); } ] } }); await instance({context}); }); test('init allows modifications', withServer, async (t, server, got) => { server.get('/', (request, response) => { response.end(request.headers.foo); }); const {body} = await got('', { headers: {}, hooks: { init: [ options => { options.headers!.foo = 'bar'; } ] } }); t.is(body, 'bar'); }); test('beforeRequest is called with options', withServer, async (t, server, got) => { server.get('/', echoHeaders); await got({ responseType: 'json', hooks: { beforeRequest: [ options => { t.is(options.url.pathname, '/'); t.is(options.url.hostname, 'localhost'); } ] } }); }); test('beforeRequest allows modifications', withServer, async (t, server, got) => { server.get('/', echoHeaders); const {body} = await got>({ responseType: 'json', hooks: { beforeRequest: [ options => { options.headers.foo = 'bar'; } ] } }); t.is(body.foo, 'bar'); }); test('returning HTTP response from a beforeRequest hook', withServer, async (t, server, got) => { server.get('/', echoUrl); const {statusCode, headers, body} = await got({ hooks: { beforeRequest: [ () => { return new Responselike( 200, {foo: 'bar'}, Buffer.from('Hi!'), '' ); } ] } }); t.is(statusCode, 200); t.is(headers.foo, 'bar'); t.is(body, 'Hi!'); }); test('beforeRedirect is called with options and response', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/redirect', redirectEndpoint); await got('redirect', { responseType: 'json', hooks: { beforeRedirect: [ (options, response) => { t.is(options.url.pathname, '/'); t.is(options.url.hostname, 'localhost'); t.is(response.statusCode, 302); t.is(new URL(response.url).pathname, '/redirect'); t.is(response.redirectUrls.length, 1); } ] } }); }); test('beforeRedirect allows modifications', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/redirect', redirectEndpoint); const {body} = await got>('redirect', { responseType: 'json', hooks: { beforeRedirect: [ options => { options.headers.foo = 'bar'; } ] } }); t.is(body.foo, 'bar'); }); test('beforeRetry is called with options', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/retry', retryEndpoint); const context = {}; await got('retry', { responseType: 'json', retry: 1, throwHttpErrors: false, context, hooks: { beforeRetry: [ (options, error, retryCount) => { t.is(options.url.hostname, 'localhost'); t.is(options.context, context); t.truthy(error); t.true(retryCount! >= 1); } ] } }); }); test('beforeRetry allows modifications', withServer, async (t, server, got) => { server.get('/', echoHeaders); server.get('/retry', retryEndpoint); const {body} = await got>('retry', { responseType: 'json', hooks: { beforeRetry: [ options => { options.headers.foo = 'bar'; } ] } }); t.is(body.foo, 'bar'); }); test('beforeRetry allows stream body if different from original', withServer, async (t, server, got) => { server.post('/retry', async (request, response) => { if (request.headers.foo) { response.send('test'); } else { response.statusCode = 500; } response.end(); }); const generateBody = () => { const form = new FormData(); form.append('A', 'B'); return form; }; const {body} = await got.post('retry', { body: generateBody(), retry: { methods: ['POST'] }, hooks: { beforeRetry: [ options => { const form = generateBody(); options.body = form; options.headers['content-type'] = `multipart/form-data; boundary=${form.getBoundary()}`; options.headers.foo = 'bar'; } ] } }); t.is(body, 'test'); }); test('afterResponse is called with response', withServer, async (t, server, got) => { server.get('/', echoHeaders); await got({ responseType: 'json', hooks: { afterResponse: [ response => { t.is(typeof response.body, 'object'); return response; } ] } }); }); test('afterResponse allows modifications', withServer, async (t, server, got) => { server.get('/', echoHeaders); const {body} = await got>({ responseType: 'json', hooks: { afterResponse: [ response => { response.body = {hello: 'world'}; return response; } ] } }); t.is(body.hello, 'world'); }); test('afterResponse allows to retry', withServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.headers.token !== 'unicorn') { response.statusCode = 401; } response.end(); }); const {statusCode} = await got({ hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token: 'unicorn' } }); } return response; } ] } }); t.is(statusCode, 200); }); test('cancelling the request after retrying in a afterResponse hook', withServer, async (t, server, got) => { let requests = 0; server.get('/', (_request, response) => { requests++; response.end(); }); const gotPromise = got({ hooks: { afterResponse: [ (_response, retryWithMergedOptions) => { const promise = retryWithMergedOptions({ headers: { token: 'unicorn' } }); gotPromise.cancel(); return promise; } ] }, retry: { calculateDelay: () => 1 } }); await t.throwsAsync(gotPromise); await delay(100); t.is(requests, 1); }); test('afterResponse allows to retry - `beforeRetry` hook', withServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.headers.token !== 'unicorn') { response.statusCode = 401; } response.end(); }); let isCalled = false; const {statusCode} = await got({ hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token: 'unicorn' } }); } return response; } ], beforeRetry: [ options => { t.truthy(options); isCalled = true; } ] } }); t.is(statusCode, 200); t.true(isCalled); }); test('no infinity loop when retrying on afterResponse', withServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.headers.token !== 'unicorn') { response.statusCode = 401; } response.end(); }); await t.throwsAsync(got({ retry: 0, hooks: { afterResponse: [ (_response, retryWithMergedOptions) => { return retryWithMergedOptions({ headers: { token: 'invalid' } }); } ] } }), {instanceOf: got.HTTPError, message: 'Response code 401 (Unauthorized)'}); }); test('throws on afterResponse retry failure', withServer, async (t, server, got) => { let didVisit401then500: boolean; server.get('/', (_request, response) => { if (didVisit401then500) { response.statusCode = 500; } else { didVisit401then500 = true; response.statusCode = 401; } response.end(); }); await t.throwsAsync(got({ retry: 1, hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token: 'unicorn' } }); } return response; } ] } }), {instanceOf: got.HTTPError, message: 'Response code 500 (Internal Server Error)'}); }); test('doesn\'t throw on afterResponse retry HTTP failure if throwHttpErrors is false', withServer, async (t, server, got) => { let didVisit401then500: boolean; server.get('/', (_request, response) => { if (didVisit401then500) { response.statusCode = 500; } else { didVisit401then500 = true; response.statusCode = 401; } response.end(); }); const {statusCode} = await got({ throwHttpErrors: false, retry: 1, hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token: 'unicorn' } }); } return response; } ] } }); t.is(statusCode, 500); }); test('throwing in a beforeError hook - promise', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await t.throwsAsync(got({ hooks: { afterResponse: [ () => { throw error; } ], beforeError: [ (): never => { throw new Error('foobar'); }, () => { throw new Error('This shouldn\'t be called at all'); } ] } }), {message: 'foobar'}); }); test('throwing in a beforeError hook - stream', withServer, async (t, _server, got) => { await t.throwsAsync(getStream(got.stream({ hooks: { beforeError: [ () => { throw new Error('foobar'); }, () => { throw new Error('This shouldn\'t be called at all'); } ] } })), {message: 'foobar'}); }); test('beforeError is called with an error - promise', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await t.throwsAsync(got({ hooks: { afterResponse: [ () => { throw error; } ], beforeError: [error2 => { t.true(error2 instanceof Error); return error2; }] } }), {message: errorString}); }); test('beforeError is called with an error - stream', withServer, async (t, _server, got) => { await t.throwsAsync(getStream(got.stream({ hooks: { beforeError: [error2 => { t.true(error2 instanceof Error); return error2; }] } })), {message: 'Response code 404 (Not Found)'}); }); test('beforeError allows modifications', async t => { const errorString2 = 'foobar'; await t.throwsAsync(got('https://example.com', { request: () => { throw error; }, hooks: { beforeError: [ error => { const newError = new Error(errorString2); return new RequestError(newError.message, newError, error.options); } ] } }), {message: errorString2}); }); test('does not break on `afterResponse` hook with JSON mode', withServer, async (t, server, got) => { server.get('/foobar', echoHeaders); await t.notThrowsAsync(got('', { hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 404) { const url = new URL('/foobar', response.url); return retryWithMergedOptions({url}); } return response; } ] }, responseType: 'json' })); }); test('catches HTTPErrors', withServer, async (t, _server, got) => { t.plan(2); await t.throwsAsync(got({ hooks: { beforeError: [ error => { t.true(error instanceof got.HTTPError); return error; } ] } })); }); test('timeout can be modified using a hook', withServer, async (t, server, got) => { server.get('/', () => {}); await t.throwsAsync(got({ timeout: 1000, hooks: { beforeRequest: [ options => { options.timeout.request = 500; } ] }, retry: 0 }), {message: 'Timeout awaiting \'request\' for 500ms'}); }); test('beforeRequest hook is called before each request', withServer, async (t, server, got) => { server.post('/', echoUrl); server.post('/redirect', redirectEndpoint); const buffer = Buffer.from('Hello, Got!'); let counts = 0; await got.post('redirect', { body: buffer, hooks: { beforeRequest: [ options => { counts++; t.is(options.headers['content-length'], String(buffer.length)); } ] } }); t.is(counts, 2); }); test('beforeError emits valid promise `HTTPError`s', async t => { t.plan(3); nock('https://ValidHTTPErrors.com').get('/').reply(() => [422, 'no']); const instance = got.extend({ hooks: { beforeError: [ error => { t.true(error instanceof HTTPError); t.truthy(error.response!.body); return error; } ] }, retry: 0 }); await t.throwsAsync(instance('https://ValidHTTPErrors.com')); }); test('hooks are not duplicated', withServer, async (t, _server, got) => { let calls = 0; await t.throwsAsync(got({ hooks: { beforeError: [ error => { calls++; return error; } ] }, retry: 0 }), {message: 'Response code 404 (Not Found)'}); t.is(calls, 1); }); test('async afterResponse allows to retry with allowGetBody and json payload', withServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.headers.token !== 'unicorn') { response.statusCode = 401; } response.end(); }); const {statusCode} = await got({ allowGetBody: true, json: {hello: 'world'}, hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({headers: {token: 'unicorn'}}); } return response; } ] }, retry: 0, throwHttpErrors: false }); t.is(statusCode, 200); }); test('beforeRequest hook respect `agent` option', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {agent, spy} = createAgentSpy(HttpAgent); t.truthy((await got({ hooks: { beforeRequest: [ options => { options.agent = { http: agent }; } ] } })).body); t.true(spy.calledOnce); // Make sure to close all open sockets agent.destroy(); }); test('beforeRequest hook respect `url` option', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ko'); }); server.get('/changed', (_request, response) => { response.end('ok'); }); t.is((await got(server.hostname, { hooks: { beforeRequest: [ options => { options.url = new URL(server.url + '/changed'); } ] } })).body, 'ok'); }); test('no duplicate hook calls in single-page paginated requests', withServer, async (t, server, got) => { server.get('/get', (_request, response) => { response.end('i <3 koalas'); }); let beforeHookCount = 0; let beforeHookCountAdditional = 0; let afterHookCount = 0; let afterHookCountAdditional = 0; const hooks = { beforeRequest: [ () => { beforeHookCount++; } ], afterResponse: [ (response: any) => { afterHookCount++; return response; } ] }; // Test only one request const instance = got.extend({ hooks, pagination: { paginate: () => false, countLimit: 2009, transform: response => [response] } }); await instance.paginate.all('get'); t.is(beforeHookCount, 1); t.is(afterHookCount, 1); await instance.paginate.all('get', { hooks: { beforeRequest: [ () => { beforeHookCountAdditional++; } ], afterResponse: [ (response: any) => { afterHookCountAdditional++; return response; } ] } }); t.is(beforeHookCount, 2); t.is(afterHookCount, 2); t.is(beforeHookCountAdditional, 1); t.is(afterHookCountAdditional, 1); await got.paginate.all('get', { hooks, pagination: { paginate: () => false, transform: response => [response] } }); t.is(beforeHookCount, 3); t.is(afterHookCount, 3); }); test('no duplicate hook calls in sequential paginated requests', withServer, async (t, server, got) => { server.get('/get', (_request, response) => { response.end('i <3 unicorns'); }); let requestNumber = 0; let beforeHookCount = 0; let afterHookCount = 0; const hooks = { beforeRequest: [ () => { beforeHookCount++; } ], afterResponse: [ (response: any) => { afterHookCount++; return response; } ] }; // Test only two requests, one after another const paginate = () => requestNumber++ === 0 ? {} : false; const instance = got.extend({ hooks, pagination: { paginate, countLimit: 2009, transform: response => [response] } }); await instance.paginate.all('get'); t.is(beforeHookCount, 2); t.is(afterHookCount, 2); requestNumber = 0; await got.paginate.all('get', { hooks, pagination: { paginate, transform: response => [response] } }); t.is(beforeHookCount, 4); t.is(afterHookCount, 4); }); test('intentional duplicate hooks in pagination with extended instance', withServer, async (t, server, got) => { server.get('/get', (_request, response) => { response.end('<3'); }); let beforeCount = 0; // Number of times the hooks from `extend` are called let afterCount = 0; let beforeCountAdditional = 0; // Number of times the added hooks are called let afterCountAdditional = 0; const beforeHook = () => { beforeCount++; }; const afterHook = (response: any) => { afterCount++; return response; }; const instance = got.extend({ hooks: { beforeRequest: [ beforeHook, beforeHook ], afterResponse: [ afterHook, afterHook ] }, pagination: { paginate: () => false, countLimit: 2009, transform: response => [response] } }); // Add duplicate hooks when calling paginate const beforeHookAdditional = () => { beforeCountAdditional++; }; const afterHookAdditional = (response: any) => { afterCountAdditional++; return response; }; await instance.paginate.all('get', { hooks: { beforeRequest: [ beforeHook, beforeHookAdditional, beforeHookAdditional ], afterResponse: [ afterHook, afterHookAdditional, afterHookAdditional ] } }); t.is(beforeCount, 3); t.is(afterCount, 3); t.is(beforeCountAdditional, 2); t.is(afterCountAdditional, 2); }); test('no duplicate hook calls when returning original request options', withServer, async (t, server, got) => { server.get('/get', (_request, response) => { response.end('i <3 unicorns'); }); let requestNumber = 0; let beforeHookCount = 0; let afterHookCount = 0; const hooks = { beforeRequest: [ () => { beforeHookCount++; } ], afterResponse: [ (response: any) => { afterHookCount++; return response; } ] }; // Test only two requests, one after another const paginate = (response: Response) => requestNumber++ === 0 ? response.request.options : false; const instance = got.extend({ hooks, pagination: { paginate, countLimit: 2009, transform: response => [response] } }); await instance.paginate.all('get'); t.is(beforeHookCount, 2); t.is(afterHookCount, 2); requestNumber = 0; await got.paginate.all('get', { hooks, pagination: { paginate, transform: response => [response] } }); t.is(beforeHookCount, 4); t.is(afterHookCount, 4); }); test('`beforeRequest` change body', withServer, async (t, server, got) => { server.post('/', echoBody); const response = await got.post({ json: {payload: 'old'}, hooks: { beforeRequest: [ options => { options.body = JSON.stringify({payload: 'new'}); options.headers['content-length'] = options.body.length.toString(); } ] } }); t.is(JSON.parse(response.body).payload, 'new'); }); got-11.8.5/test/http.ts000066400000000000000000000215471424347352000147220ustar00rootroot00000000000000import {STATUS_CODES, Agent} from 'http'; import test from 'ava'; import {Handler} from 'express'; import {isIPv4, isIPv6} from 'net'; import nock = require('nock'); import getStream = require('get-stream'); import pEvent from 'p-event'; import got, {HTTPError, UnsupportedProtocolError, CancelableRequest, ReadError} from '../source'; import withServer from './helpers/with-server'; import os = require('os'); const IPv6supported = Object.values(os.networkInterfaces()).some(iface => iface?.some(addr => !addr.internal && addr.family === 'IPv6')); const testIPv6 = (IPv6supported && process.env.TRAVIS_DIST !== 'bionic' && process.env.TRAVIS_DIST !== 'focal') ? test : test.skip; const echoIp: Handler = (request, response) => { const address = request.connection.remoteAddress; if (address === undefined) { response.end(); return; } // IPv4 address mapped to IPv6 response.end(address === '::ffff:127.0.0.1' ? '127.0.0.1' : address); }; const echoBody: Handler = async (request, response) => { response.end(await getStream(request)); }; test('simple request', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); t.is((await got('')).body, 'ok'); }); test('empty response', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end(); }); t.is((await got('')).body, ''); }); test('response has `requestUrl` property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); server.get('/empty', (_request, response) => { response.end(); }); t.is((await got('')).requestUrl, `${server.url}/`); t.is((await got('empty')).requestUrl, `${server.url}/empty`); }); test('http errors have `response` property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end('not'); }); const error = await t.throwsAsync(got(''), {instanceOf: HTTPError}); t.is(error.response.statusCode, 404); t.is(error.response.body, 'not'); }); test('status code 304 doesn\'t throw', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 304; response.end(); }); const promise = got(''); await t.notThrowsAsync(promise); const {statusCode, body} = await promise; t.is(statusCode, 304); t.is(body, ''); }); test('doesn\'t throw if `options.throwHttpErrors` is false', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end('not'); }); t.is((await got({throwHttpErrors: false})).body, 'not'); }); test('invalid protocol throws', async t => { await t.throwsAsync(got('c:/nope.com').json(), { instanceOf: UnsupportedProtocolError, code: 'ERR_UNSUPPORTED_PROTOCOL', message: 'Unsupported protocol "c:"' }); }); test('custom `options.encoding`', withServer, async (t, server, got) => { const string = 'ok'; server.get('/', (_request, response) => { response.end(string); }); const data = (await got({encoding: 'base64'})).body; t.is(data, Buffer.from(string).toString('base64')); }); test('`options.encoding` doesn\'t affect streams', withServer, async (t, server, got) => { const string = 'ok'; server.get('/', (_request, response) => { response.end(string); }); const data = await getStream(got.stream({encoding: 'base64'})); t.is(data, string); }); test('`got.stream(...).setEncoding(...)` works', withServer, async (t, server, got) => { const string = 'ok'; server.get('/', (_request, response) => { response.end(string); }); const data = await getStream(got.stream('').setEncoding('base64')); t.is(data, Buffer.from(string).toString('base64')); }); test('`searchParams` option', withServer, async (t, server, got) => { server.get('/', (request, response) => { t.is(request.query.recent, 'true'); response.end('recent'); }); t.is((await got({searchParams: {recent: true}})).body, 'recent'); t.is((await got({searchParams: 'recent=true'})).body, 'recent'); }); test('response contains url', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); t.is((await got('')).url, `${server.url}/`); }); test('response contains got options', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); { const options = { username: 'foo', password: 'bar' }; const {options: normalizedOptions} = (await got(options)).request; t.is(normalizedOptions.username, options.username); t.is(normalizedOptions.password, options.password); } { const options = { username: 'foo' }; const {options: normalizedOptions} = (await got(options)).request; t.is(normalizedOptions.username, options.username); t.is(normalizedOptions.password, ''); } { const options = { password: 'bar' }; const {options: normalizedOptions} = (await got(options)).request; t.is(normalizedOptions.username, ''); t.is(normalizedOptions.password, options.password); } }); test('socket destroyed by the server throws ECONNRESET', withServer, async (t, server, got) => { server.get('/', request => { request.socket.destroy(); }); await t.throwsAsync(got('', {retry: 0}), { code: 'ECONNRESET' }); }); test('the response contains timings property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {timings} = await got(''); t.truthy(timings); t.true(timings.phases.total! >= 0); }); test('throws an error if the server aborted the request', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(200, { 'content-type': 'text/plain' }); response.write('chunk 1'); setImmediate(() => { response.write('chunk 2'); setImmediate(() => { response.destroy(); }); }); }); const error = await t.throwsAsync(got(''), { code: 'ECONNRESET', message: 'The server aborted pending request' }); t.truthy(error.response.retryCount); }); test('statusMessage fallback', async t => { nock('http://statusMessageFallback').get('/').reply(503); const {statusMessage} = await got('http://statusMessageFallback', { throwHttpErrors: false, retry: 0 }); t.is(statusMessage, STATUS_CODES[503]); }); test('does not destroy completed requests', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('content-encoding', 'gzip'); response.end(''); }); const options = { agent: { http: new Agent({keepAlive: true}) }, retry: 0 }; const stream = got.stream(options); stream.resume(); const endPromise = pEvent(stream, 'end'); const socket = await pEvent(stream, 'socket'); const closeListener = () => { t.fail('Socket has been destroyed'); }; socket.once('close', closeListener); await new Promise(resolve => { setTimeout(resolve, 10); }); socket.off('close', closeListener); await endPromise; options.agent.http.destroy(); t.pass(); }); testIPv6('IPv6 request', withServer, async (t, server) => { server.get('/ok', echoIp); const response = await got(`http://[::1]:${server.port}/ok`); t.is(response.body, '::1'); }); test('DNS auto', withServer, async (t, server, got) => { server.get('/ok', echoIp); const response = await got('ok', { dnsLookupIpVersion: 'auto' }); t.true(isIPv4(response.body)); }); test('DNS IPv4', withServer, async (t, server, got) => { server.get('/ok', echoIp); const response = await got('ok', { dnsLookupIpVersion: 'ipv4' }); t.true(isIPv4(response.body)); }); // Travis CI Ubuntu Focal VM does not resolve IPv6 hostnames testIPv6('DNS IPv6', withServer, async (t, server, got) => { server.get('/ok', echoIp); const response = await got('ok', { dnsLookupIpVersion: 'ipv6' }); t.true(isIPv6(response.body)); }); test('invalid `dnsLookupIpVersion`', withServer, async (t, server, got) => { server.get('/ok', echoIp); await t.throwsAsync(got('ok', { dnsLookupIpVersion: 'test' } as any)); }); test.serial('deprecated `family` option', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await new Promise(resolve => { let request: CancelableRequest; (async () => { const warning = await pEvent(process, 'warning'); t.is(warning.name, 'DeprecationWarning'); request!.cancel(); resolve(); })(); (async () => { request = got({ family: '4' } as any); try { await request; t.fail(); } catch { t.true(request!.isCanceled); } resolve(); })(); }); }); test('JSON request custom stringifier', withServer, async (t, server, got) => { server.post('/', echoBody); const payload = {a: 'b'}; const customStringify = (object: any) => JSON.stringify({...object, c: 'd'}); t.deepEqual((await got.post({ stringifyJson: customStringify, json: payload })).body, customStringify(payload)); }); got-11.8.5/test/https.ts000066400000000000000000000274121424347352000151020ustar00rootroot00000000000000import test from 'ava'; import got, {CancelableRequest} from '../source'; import {withHttpsServer} from './helpers/with-server'; import {DetailedPeerCertificate} from 'tls'; import pEvent from 'p-event'; import pify = require('pify'); import pem = require('pem'); const createPrivateKey = pify(pem.createPrivateKey); const createCSR = pify(pem.createCSR); const createCertificate = pify(pem.createCertificate); const createPkcs12 = pify(pem.createPkcs12); test('https request without ca', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); t.truthy((await got({ https: { certificateAuthority: [], rejectUnauthorized: false } })).body); }); test('https request with ca', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {body} = await got({}); t.is(body, 'ok'); }); test('https request with ca and afterResponse hook', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const warningListener = (warning: any) => { if ( warning.name === 'DeprecationWarning' && warning.message === 'Got: "options.ca" was never documented, please use ' + '"options.https.certificateAuthority"' ) { process.off('warning', warningListener); t.fail('unexpected deprecation warning'); } }; process.once('warning', warningListener); let shouldRetry = true; const {body} = await got({ hooks: { afterResponse: [ (response, retry) => { if (shouldRetry) { shouldRetry = false; return retry({}); } return response; } ] } }); t.is(body, 'ok'); }); test('https request with `checkServerIdentity` OK', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const {body} = await got({ https: { checkServerIdentity: (hostname: string, certificate: DetailedPeerCertificate) => { t.is(hostname, 'localhost'); t.is(certificate.subject.CN, 'localhost'); t.is(certificate.issuer.CN, 'authority'); } } }); t.is(body, 'ok'); }); test('https request with `checkServerIdentity` NOT OK', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); const promise = got({ https: { checkServerIdentity: (hostname: string, certificate: DetailedPeerCertificate) => { t.is(hostname, 'localhost'); t.is(certificate.subject.CN, 'localhost'); t.is(certificate.issuer.CN, 'authority'); return new Error('CUSTOM_ERROR'); } } }); await t.throwsAsync( promise, { message: 'CUSTOM_ERROR' } ); }); // The built-in `openssl` on macOS does not support negative days. { const testFn = process.platform === 'darwin' ? test.skip : test; testFn('https request with expired certificate', withHttpsServer({days: -1}), async (t, _server, got) => { await t.throwsAsync( got({}), { code: 'CERT_HAS_EXPIRED' } ); }); } test('https request with wrong host', withHttpsServer({commonName: 'not-localhost.com'}), async (t, _server, got) => { await t.throwsAsync( got({}), { code: 'ERR_TLS_CERT_ALTNAME_INVALID' } ); }); test('http2', async t => { const promise = got('https://httpbin.org/anything', { http2: true }); const {headers, body} = await promise; await promise.json(); // @ts-expect-error Pseudo headers may not be strings t.is(headers[':status'], 200); t.is(typeof body, 'string'); }); test.serial('deprecated `rejectUnauthorized` option', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await new Promise(resolve => { let request: CancelableRequest; (async () => { const warning = await pEvent(process, 'warning'); t.is(warning.name, 'DeprecationWarning'); request!.cancel(); resolve(); })(); (async () => { request = got({ rejectUnauthorized: false }); try { await request; t.fail(); } catch { t.true(request!.isCanceled); } resolve(); })(); }); }); test.serial('non-deprecated `rejectUnauthorized` option', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); (async () => { const warning = await pEvent(process, 'warning'); t.not(warning.name, 'DeprecationWarning'); })(); await got({ https: { rejectUnauthorized: false } }); t.pass(); }); test.serial('no double deprecated warning', withHttpsServer(), async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); (async () => { const warning = await pEvent(process, 'warning'); t.is(warning.name, 'DeprecationWarning'); })(); await got({ rejectUnauthorized: false }); (async () => { const warning = await pEvent(process, 'warning'); t.not(warning.name, 'DeprecationWarning'); })(); await got({ rejectUnauthorized: false }); t.pass(); }); test('client certificate', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const clientCSRResult = await createCSR({commonName: 'client'}); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response: any = await got({ https: { key: clientKey, certificate: clientCert } }).json(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); test('invalid client certificate (self-signed)', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const clientCSRResult = await createCSR({commonName: 'other-client'}); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, selfSigned: true }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response: any = await got({ https: { key: clientKey, certificate: clientCert } }).json(); t.is(response.authorized, false); }); test('invalid client certificate (other CA)', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const caCSRResult = await createCSR({commonName: 'other-authority'}); const caResult = await createCertificate({ csr: caCSRResult.csr, clientKey: caCSRResult.clientKey, selfSigned: true }); const caKey = caResult.clientKey; const caCert = caResult.certificate; const clientCSRResult = await createCSR({commonName: 'other-client'}); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, serviceKey: caKey, serviceCertificate: caCert }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const response: any = await got({ https: { key: clientKey, certificate: clientCert } }).json(); t.false(response.authorized); t.is(response.peerCertificate.subject.CN, 'other-client'); t.is(response.peerCertificate.issuer.CN, 'other-authority'); }); test('key passphrase', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const {key: clientKey} = await createPrivateKey(2048, { cipher: 'aes256', password: 'randomPassword' }); const clientCSRResult = await createCSR({ // eslint-disable-next-line object-shorthand clientKey: clientKey, clientKeyPassword: 'randomPassword', commonName: 'client' }); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, clientKeyPassword: 'randomPassword', serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert }); const clientCert = clientResult.certificate; const response: any = await got({ https: { key: clientKey, passphrase: 'randomPassword', certificate: clientCert } }).json(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); test('invalid key passphrase', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const {key: clientKey} = await createPrivateKey(2048, { cipher: 'aes256', password: 'randomPassword' }); const clientCSRResult = await createCSR({ // eslint-disable-next-line object-shorthand clientKey: clientKey, clientKeyPassword: 'randomPassword', commonName: 'client' }); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, clientKeyPassword: 'randomPassword', serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert }); const clientCert = clientResult.certificate; const NODE_10 = process.versions.node.split('.')[0] === '10'; const request = got({ https: { key: clientKey, passphrase: 'wrongPassword', certificate: clientCert } }); // Node.JS 10 does not have an error code, it only has a mesage if (NODE_10) { try { await request; t.fail(); } catch (error) { t.true((error.message as string).includes('bad decrypt'), error.message); } } else { await t.throwsAsync(request, { code: 'ERR_OSSL_EVP_BAD_DECRYPT' }); } }); test('client certificate PFX', withHttpsServer(), async (t, server, got) => { server.get('/', (request, response) => { const peerCertificate = (request.socket as any).getPeerCertificate(true); peerCertificate.issuerCertificate = undefined; // Circular structure response.json({ authorized: (request.socket as any).authorized, peerCertificate }); }); const clientCSRResult = await createCSR({commonName: 'client'}); const clientResult = await createCertificate({ csr: clientCSRResult.csr, clientKey: clientCSRResult.clientKey, serviceKey: (server as any).caKey, serviceCertificate: (server as any).caCert }); // eslint-disable-next-line prefer-destructuring const clientKey = clientResult.clientKey; const clientCert = clientResult.certificate; const {pkcs12} = await createPkcs12(clientKey, clientCert, 'randomPassword'); const response: any = await got({ https: { pfx: pkcs12, passphrase: 'randomPassword' } }).json(); t.true(response.authorized); t.is(response.peerCertificate.subject.CN, 'client'); t.is(response.peerCertificate.issuer.CN, 'authority'); }); got-11.8.5/test/merge-instances.ts000066400000000000000000000116471424347352000170270ustar00rootroot00000000000000import test from 'ava'; import {Handler} from 'express'; import got, {BeforeRequestHook, Got, Headers, NormalizedOptions} from '../source'; import withServer from './helpers/with-server'; const echoHeaders: Handler = (request, response) => { response.end(JSON.stringify(request.headers)); }; test('merging instances', withServer, async (t, server) => { server.get('/', echoHeaders); const instanceA = got.extend({headers: {unicorn: 'rainbow'}}); const instanceB = got.extend({prefixUrl: server.url}); const merged = instanceA.extend(instanceB); const headers = await merged('').json(); t.is(headers.unicorn, 'rainbow'); t.not(headers['user-agent'], undefined); }); test('merges default handlers & custom handlers', withServer, async (t, server) => { server.get('/', echoHeaders); const instanceA = got.extend({headers: {unicorn: 'rainbow'}}); const instanceB = got.extend({ handlers: [ (options, next) => { options.headers.cat = 'meow'; return next(options); } ] }); const merged = instanceA.extend(instanceB); const headers = await merged(server.url).json(); t.is(headers.unicorn, 'rainbow'); t.is(headers.cat, 'meow'); }); test('merging one group & one instance', withServer, async (t, server) => { server.get('/', echoHeaders); const instanceA = got.extend({headers: {dog: 'woof'}}); const instanceB = got.extend({headers: {cat: 'meow'}}); const instanceC = got.extend({headers: {bird: 'tweet'}}); const instanceD = got.extend({headers: {mouse: 'squeek'}}); const merged = instanceA.extend(instanceB, instanceC); const doubleMerged = merged.extend(instanceD); const headers = await doubleMerged(server.url).json(); t.is(headers.dog, 'woof'); t.is(headers.cat, 'meow'); t.is(headers.bird, 'tweet'); t.is(headers.mouse, 'squeek'); }); test('merging two groups of merged instances', withServer, async (t, server) => { server.get('/', echoHeaders); const instanceA = got.extend({headers: {dog: 'woof'}}); const instanceB = got.extend({headers: {cat: 'meow'}}); const instanceC = got.extend({headers: {bird: 'tweet'}}); const instanceD = got.extend({headers: {mouse: 'squeek'}}); const groupA = instanceA.extend(instanceB); const groupB = instanceC.extend(instanceD); const merged = groupA.extend(groupB); const headers = await merged(server.url).json(); t.is(headers.dog, 'woof'); t.is(headers.cat, 'meow'); t.is(headers.bird, 'tweet'); t.is(headers.mouse, 'squeek'); }); test('hooks are merged', t => { const getBeforeRequestHooks = (instance: Got): BeforeRequestHook[] => instance.defaults.options.hooks.beforeRequest; const instanceA = got.extend({hooks: { beforeRequest: [ options => { options.headers.dog = 'woof'; } ] }}); const instanceB = got.extend({hooks: { beforeRequest: [ options => { options.headers.cat = 'meow'; } ] }}); const merged = instanceA.extend(instanceB); t.deepEqual(getBeforeRequestHooks(merged), getBeforeRequestHooks(instanceA).concat(getBeforeRequestHooks(instanceB))); }); test('default handlers are not duplicated', t => { const instance = got.extend(got); t.is(instance.defaults.handlers.length, 1); }); test('URL is not polluted', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await got({ username: 'foo' }); const {options: normalizedOptions} = (await got({})).request; t.is(normalizedOptions.username, ''); }); test('merging instances with HTTPS options', t => { const instanceA = got.extend({https: { rejectUnauthorized: true, certificate: 'FIRST' }}); const instanceB = got.extend({https: { certificate: 'SECOND' }}); const merged = instanceA.extend(instanceB); t.true(merged.defaults.options.https?.rejectUnauthorized); t.is(merged.defaults.options.https?.certificate, 'SECOND'); }); test('merging instances with HTTPS options undefined', t => { const instanceA = got.extend({https: { rejectUnauthorized: true, certificate: 'FIRST' }}); const instanceB = got.extend({https: { certificate: undefined }}); const merged = instanceA.extend(instanceB); t.true(merged.defaults.options.https?.rejectUnauthorized); t.is(merged.defaults.options.https?.certificate, undefined); }); test('accepts options for promise API', t => { got.extend({ hooks: { beforeRequest: [ (options: NormalizedOptions): void => { options.responseType = 'buffer'; } ] } }); t.pass(); }); test('merging `prefixUrl`', t => { const prefixUrl = 'http://example.com/'; const instanceA = got.extend({headers: {unicorn: 'rainbow'}}); const instanceB = got.extend({prefixUrl}); const mergedAonB = instanceB.extend(instanceA); const mergedBonA = instanceA.extend(instanceB); t.is(mergedAonB.defaults.options.prefixUrl, ''); t.is(mergedBonA.defaults.options.prefixUrl, prefixUrl); t.is(instanceB.extend({}).defaults.options.prefixUrl, prefixUrl); t.is(instanceB.extend({prefixUrl: undefined}).defaults.options.prefixUrl, prefixUrl); }); got-11.8.5/test/normalize-arguments.ts000066400000000000000000000054571424347352000177500ustar00rootroot00000000000000import {URL, URLSearchParams} from 'url'; import test from 'ava'; import got from '../source'; test('should merge options replacing responseType', t => { const responseType = 'json'; const options = got.mergeOptions(got.defaults.options, { responseType }); t.is(options.responseType, responseType); }); test('no duplicated searchParams values', t => { const options = got.mergeOptions(got.defaults.options, { searchParams: 'string=true&noDuplication=true' }, { searchParams: new URLSearchParams({ instance: 'true', noDuplication: 'true' }) }); t.is(options.searchParams?.get('string'), 'true'); t.is(options.searchParams?.get('instance'), 'true'); t.is(options.searchParams?.getAll('noDuplication').length, 1); }); test('should copy non-numerable properties', t => { const options = { json: {hello: '123'} }; const merged = got.mergeOptions(got.defaults.options, options); const mergedTwice = got.mergeOptions(got.defaults.options, merged); t.is(mergedTwice.json, options.json); }); test('should replace URLs', t => { const options = got.mergeOptions({ url: new URL('http://localhost:41285'), searchParams: new URLSearchParams('page=0') }, { url: 'http://localhost:41285/?page=1', searchParams: undefined }); const otherOptions = got.mergeOptions({ url: new URL('http://localhost:41285'), searchParams: { page: 0 } }, { url: 'http://localhost:41285/?page=1', searchParams: undefined }); t.is(options.url.href, 'http://localhost:41285/?page=1'); t.is(otherOptions.url.href, 'http://localhost:41285/?page=1'); }); test('should get username and password from the URL', t => { const options = got.mergeOptions({ url: 'http://user:pass@localhost:41285' }); t.is(options.username, 'user'); t.is(options.password, 'pass'); }); test('should get username and password from the options', t => { const options = got.mergeOptions({ url: 'http://user:pass@localhost:41285', username: 'user_OPT', password: 'pass_OPT' }); t.is(options.username, 'user_OPT'); t.is(options.password, 'pass_OPT'); }); test('should get username and password from the merged options', t => { const options = got.mergeOptions( { url: 'http://user:pass@localhost:41285' }, { username: 'user_OPT_MERGE', password: 'pass_OPT_MERGE' } ); t.is(options.username, 'user_OPT_MERGE'); t.is(options.password, 'pass_OPT_MERGE'); }); test('null value in search params means empty', t => { const options = got.mergeOptions({ url: new URL('http://localhost'), searchParams: { foo: null } }); t.is(options.url.href, 'http://localhost/?foo='); }); test('undefined value in search params means it does not exist', t => { const options = got.mergeOptions({ url: new URL('http://localhost'), searchParams: { foo: undefined } }); t.is(options.url.href, 'http://localhost/'); }); got-11.8.5/test/pagination.ts000066400000000000000000000355161424347352000160750ustar00rootroot00000000000000import {URL} from 'url'; import test from 'ava'; import delay = require('delay'); import getStream = require('get-stream'); import got, {Response} from '../source'; import withServer, {withBodyParsingServer} from './helpers/with-server'; import {ExtendedHttpTestServer} from './helpers/create-http-test-server'; const thrower = (): any => { throw new Error('This should not be called'); }; const resetPagination = { paginate: undefined, transform: undefined, filter: undefined, shouldContinue: undefined }; const attachHandler = (server: ExtendedHttpTestServer, count: number): void => { server.get('/', (request, response) => { const searchParameters = new URLSearchParams(request.url.split('?')[1]); const page = Number(searchParameters.get('page')) || 1; if (page < count) { response.setHeader('link', `<${server.url}/?page=${page + 1}>; rel="next"`); } response.end(`[${page <= count ? page : ''}]`); }); }; test('the link header has no next value', withServer, async (t, server, got) => { const items = [1]; server.get('/', (_request, response) => { response.setHeader('link', '; rel="prev"'); response.end(JSON.stringify(items)); }); const received = await got.paginate.all(''); t.deepEqual(received, items); }); test('retrieves all elements', withServer, async (t, server, got) => { attachHandler(server, 2); const result = await got.paginate.all(''); t.deepEqual(result, [1, 2]); }); test('retrieves all elements with JSON responseType', withServer, async (t, server, got) => { attachHandler(server, 2); const result = await got.extend({ responseType: 'json' }).paginate.all(''); t.deepEqual(result, [1, 2]); }); test('points to defaults when extending Got without custom `pagination`', withServer, async (t, server, got) => { attachHandler(server, 2); const result = await got.extend().paginate.all(''); t.deepEqual(result, [1, 2]); }); test('pagination options can be extended', withServer, async (t, server, got) => { attachHandler(server, 2); const result = await got.extend({ pagination: { shouldContinue: () => false } }).paginate.all(''); t.deepEqual(result, []); }); test('filters elements', withServer, async (t, server, got) => { attachHandler(server, 3); const result = await got.paginate.all({ pagination: { filter: (element: number, allItems: number[], currentItems: number[]) => { t.true(Array.isArray(allItems)); t.true(Array.isArray(currentItems)); return element !== 2; } } }); t.deepEqual(result, [1, 3]); }); test('parses elements', withServer, async (t, server, got) => { attachHandler(server, 100); const result = await got.paginate.all('?page=100', { pagination: { transform: response => [response.body.length] } }); t.deepEqual(result, [5]); }); test('parses elements - async function', withServer, async (t, server, got) => { attachHandler(server, 100); const result = await got.paginate.all('?page=100', { pagination: { transform: async response => [response.body.length] } }); t.deepEqual(result, [5]); }); test('custom paginate function', withServer, async (t, server, got) => { attachHandler(server, 3); const result = await got.paginate.all({ pagination: { paginate: response => { const url = new URL(response.url); if (url.search === '?page=3') { return false; } url.search = '?page=3'; return {url}; } } }); t.deepEqual(result, [1, 3]); }); test('custom paginate function using allItems', withServer, async (t, server, got) => { attachHandler(server, 3); const result = await got.paginate.all({ pagination: { paginate: (_response, allItems: number[]) => { if (allItems.length === 2) { return false; } return {path: '/?page=3'}; } } }); t.deepEqual(result, [1, 3]); }); test('custom paginate function using currentItems', withServer, async (t, server, got) => { attachHandler(server, 3); const result = await got.paginate.all({ pagination: { paginate: (_response, _allItems: number[], currentItems: number[]) => { if (currentItems[0] === 3) { return false; } return {path: '/?page=3'}; } } }); t.deepEqual(result, [1, 3]); }); test('iterator works', withServer, async (t, server, got) => { attachHandler(server, 5); const results: number[] = []; for await (const item of got.paginate('')) { results.push(item); } t.deepEqual(results, [1, 2, 3, 4, 5]); }); test('iterator works #2', withServer, async (t, server, got) => { attachHandler(server, 5); const results: number[] = []; for await (const item of got.paginate.each('')) { results.push(item); } t.deepEqual(results, [1, 2, 3, 4, 5]); }); test('`shouldContinue` works', withServer, async (t, server, got) => { attachHandler(server, 2); const options = { pagination: { shouldContinue: (_item: unknown, allItems: unknown[], currentItems: unknown[]) => { t.true(Array.isArray(allItems)); t.true(Array.isArray(currentItems)); return false; } } }; const results: number[] = []; for await (const item of got.paginate(options)) { results.push(item); } t.deepEqual(results, []); }); test('`countLimit` works', withServer, async (t, server, got) => { attachHandler(server, 2); const options = { pagination: { countLimit: 1 } }; const results: number[] = []; for await (const item of got.paginate(options)) { results.push(item); } t.deepEqual(results, [1]); }); test('throws if no `pagination` option', async t => { const iterator = got.extend({ pagination: false as any }).paginate('', { prefixUrl: 'https://example.com' }); await t.throwsAsync(iterator.next(), { message: '`options.pagination` must be implemented' }); }); test('throws if the `pagination` option does not have `transform` property', async t => { const iterator = got.paginate('', { pagination: {...resetPagination}, prefixUrl: 'https://example.com' }); await t.throwsAsync(iterator.next(), { message: '`options.pagination.transform` must be implemented' }); }); test('throws if the `pagination` option does not have `shouldContinue` property', async t => { const iterator = got.paginate('', { pagination: { ...resetPagination, transform: thrower }, prefixUrl: 'https://example.com' }); await t.throwsAsync(iterator.next(), { message: '`options.pagination.shouldContinue` must be implemented' }); }); test('throws if the `pagination` option does not have `filter` property', async t => { const iterator = got.paginate('', { pagination: { ...resetPagination, transform: thrower, shouldContinue: thrower, paginate: thrower }, prefixUrl: 'https://example.com' }); await t.throwsAsync(iterator.next(), { message: '`options.pagination.filter` must be implemented' }); }); test('throws if the `pagination` option does not have `paginate` property', async t => { const iterator = got.paginate('', { pagination: { ...resetPagination, transform: thrower, shouldContinue: thrower, filter: thrower }, prefixUrl: 'https://example.com' }); await t.throwsAsync(iterator.next(), { message: '`options.pagination.paginate` must be implemented' }); }); test('ignores the `resolveBodyOnly` option', withServer, async (t, server, got) => { attachHandler(server, 2); const items = await got.paginate.all('', { resolveBodyOnly: true }); t.deepEqual(items, [1, 2]); }); test('allowGetBody sends json payload with .paginate()', withBodyParsingServer, async (t, server, got) => { server.get('/', (request, response) => { if (request.body.hello !== 'world') { response.statusCode = 400; } response.end(JSON.stringify([1, 2, 3])); }); const iterator = got.paginate({ allowGetBody: true, json: {hello: 'world'}, retry: 0 }); const results: number[] = []; for await (const item of iterator) { results.push(item); } t.deepEqual(results, [1, 2, 3]); }); test('`hooks` are not duplicated', withServer, async (t, server, got) => { let page = 1; server.get('/', (_request, response) => { response.end(JSON.stringify([page++])); }); const nopHook = () => {}; const result = await got.paginate.all({ pagination: { paginate: response => { if ((response.body as string) === '[3]') { return false; // Stop after page 3 } const {options} = response.request; const {init, beforeRequest, beforeRedirect, beforeRetry, afterResponse, beforeError} = options.hooks; const hooksCount = [init, beforeRequest, beforeRedirect, beforeRetry, afterResponse, beforeError].map(a => a.length); t.deepEqual(hooksCount, [1, 1, 1, 1, 1, 1]); return options; } }, hooks: { init: [nopHook], beforeRequest: [nopHook], beforeRedirect: [nopHook], beforeRetry: [nopHook], afterResponse: [response => response], beforeError: [error => error] } }); t.deepEqual(result, [1, 2, 3]); }); test('allowGetBody sends correct json payload with .paginate()', withServer, async (t, server, got) => { let page = 1; server.get('/', async (request, response) => { const payload = await getStream(request); try { JSON.parse(payload); } catch { response.statusCode = 422; } if (request.headers['content-length']) { t.is(Number(request.headers['content-length'] || 0), Buffer.byteLength(payload)); } response.end(JSON.stringify([page++])); }); let body = ''; const iterator = got.paginate({ allowGetBody: true, retry: 0, json: {body}, pagination: { paginate: () => { if (body.length === 2) { return false; } body += 'a'; return { json: {body} }; } } }); const results: number[] = []; for await (const item of iterator) { results.push(item); } t.deepEqual(results, [1, 2, 3]); }); test('`requestLimit` works', withServer, async (t, server, got) => { attachHandler(server, 2); const options = { pagination: { requestLimit: 1 } }; const results: number[] = []; for await (const item of got.paginate(options)) { results.push(item); } t.deepEqual(results, [1]); }); test('`backoff` works', withServer, async (t, server, got) => { attachHandler(server, 2); const backoff = 200; const asyncIterator: AsyncIterator = got.paginate('', { pagination: { backoff } }); t.is((await asyncIterator.next()).value, 1); let receivedLastOne = false; const start = Date.now(); const promise = asyncIterator.next(); (async () => { await promise; receivedLastOne = true; })(); await delay(backoff / 2); t.false(receivedLastOne); await promise; t.true(Date.now() - start >= backoff); }); test('`stackAllItems` set to true', withServer, async (t, server, got) => { attachHandler(server, 3); let itemCount = 0; const result = await got.paginate.all({ pagination: { stackAllItems: true, filter: (_item, allItems, _currentItems) => { t.is(allItems.length, itemCount); return true; }, shouldContinue: (_item, allItems, _currentItems) => { t.is(allItems.length, itemCount); return true; }, paginate: (response, allItems, currentItems) => { itemCount += 1; t.is(allItems.length, itemCount); return got.defaults.options.pagination!.paginate(response, allItems, currentItems); } } }); t.deepEqual(result, [1, 2, 3]); }); test('`stackAllItems` set to false', withServer, async (t, server, got) => { attachHandler(server, 3); const result = await got.paginate.all({ pagination: { stackAllItems: false, filter: (_item, allItems, _currentItems) => { t.is(allItems.length, 0); return true; }, shouldContinue: (_item, allItems, _currentItems) => { t.is(allItems.length, 0); return true; }, paginate: (response, allItems, currentItems) => { t.is(allItems.length, 0); return got.defaults.options.pagination!.paginate(response, allItems, currentItems); } } }); t.deepEqual(result, [1, 2, 3]); }); test('next url in json response', withServer, async (t, server, got) => { server.get('/', (request, response) => { const parameters = new URLSearchParams(request.url.slice(2)); const page = Number(parameters.get('page') ?? 0); response.end(JSON.stringify({ currentUrl: request.url, next: page < 3 ? `${server.url}/?page=${page + 1}` : undefined })); }); interface Page { currentUrl: string; next?: string; } const all = await got.paginate.all('', { searchParams: { page: 0 }, responseType: 'json', pagination: { transform: (response: Response) => { return [response.body.currentUrl]; }, paginate: (response: Response) => { const {next} = response.body; if (!next) { return false; } return { url: next, prefixUrl: '', searchParams: undefined }; } } }); t.deepEqual(all, [ '/?page=0', '/?page=1', '/?page=2', '/?page=3' ]); }); test('pagination using searchParams', withServer, async (t, server, got) => { server.get('/', (request, response) => { const parameters = new URLSearchParams(request.url.slice(2)); const page = Number(parameters.get('page') ?? 0); response.end(JSON.stringify({ currentUrl: request.url, next: page < 3 })); }); interface Page { currentUrl: string; next?: string; } const all = await got.paginate.all('', { searchParams: { page: 0 }, responseType: 'json', pagination: { transform: (response: Response) => { return [response.body.currentUrl]; }, paginate: (response: Response) => { const {next} = response.body; const previousPage = Number(response.request.options.searchParams!.get('page')); if (!next) { return false; } return { searchParams: { page: previousPage + 1 } }; } } }); t.deepEqual(all, [ '/?page=0', '/?page=1', '/?page=2', '/?page=3' ]); }); test('pagination using extended searchParams', withServer, async (t, server, got) => { server.get('/', (request, response) => { const parameters = new URLSearchParams(request.url.slice(2)); const page = Number(parameters.get('page') ?? 0); response.end(JSON.stringify({ currentUrl: request.url, next: page < 3 })); }); interface Page { currentUrl: string; next?: string; } const client = got.extend({ searchParams: { limit: 10 } }); const all = await client.paginate.all('', { searchParams: { page: 0 }, responseType: 'json', pagination: { transform: (response: Response) => { return [response.body.currentUrl]; }, paginate: (response: Response) => { const {next} = response.body; const previousPage = Number(response.request.options.searchParams!.get('page')); if (!next) { return false; } return { searchParams: { page: previousPage + 1 } }; } } }); t.deepEqual(all, [ '/?page=0&limit=10', '/?page=1&limit=10', '/?page=2&limit=10', '/?page=3&limit=10' ]); }); got-11.8.5/test/post.ts000066400000000000000000000221701424347352000147210ustar00rootroot00000000000000import {promisify} from 'util'; import stream = require('stream'); import fs = require('fs'); import path = require('path'); import test from 'ava'; import delay = require('delay'); import pEvent = require('p-event'); import {Handler} from 'express'; import getStream = require('get-stream'); import toReadableStream = require('to-readable-stream'); import got, {UploadError} from '../source'; import withServer from './helpers/with-server'; const pStreamPipeline = promisify(stream.pipeline); const defaultEndpoint: Handler = async (request, response) => { response.setHeader('method', request.method); await pStreamPipeline(request, response); }; const echoHeaders: Handler = (request, response) => { response.end(JSON.stringify(request.headers)); }; test('GET cannot have body without the `allowGetBody` option', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.get({body: 'hi'}), {message: 'The `GET` method cannot be used with a body'}); }); test('GET can have body with option allowGetBody', withServer, async (t, server, got) => { server.get('/', defaultEndpoint); await t.notThrowsAsync(got.get({body: 'hi', allowGetBody: true})); }); test('invalid body', async t => { await t.throwsAsync( // @ts-expect-error Error tests got.post('https://example.com', {body: {}}), { message: 'The `body` option must be a stream.Readable, string or Buffer' } ); }); test('sends strings', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: 'wow'}); t.is(body, 'wow'); }); test('sends Buffers', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: Buffer.from('wow')}); t.is(body, 'wow'); }); test('sends Streams', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({body: toReadableStream('wow')}); t.is(body, 'wow'); }); test('sends plain objects as forms', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ form: {such: 'wow'} }); t.is(body, 'such=wow'); }); test('does NOT support sending arrays as forms', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.post({ form: ['such', 'wow'] }), { message: 'Each query pair must be an iterable [name, value] tuple' }); }); test('sends plain objects as JSON', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ json: {such: 'wow'}, responseType: 'json' }); t.deepEqual(body, {such: 'wow'}); }); test('sends arrays as JSON', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post({ json: ['such', 'wow'], responseType: 'json' }); t.deepEqual(body, ['such', 'wow']); }); test('works with empty post response', withServer, async (t, server, got) => { server.post('/empty', (_request, response) => { response.end(); }); const {body} = await got.post('empty', {body: 'wow'}); t.is(body, ''); }); test('`content-length` header with string body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: 'wow'}); const headers = JSON.parse(body); t.is(headers['content-length'], '3'); }); test('`content-length` header with json body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({json: {foo: 'bar'}}); const headers = JSON.parse(body); t.is(headers['content-length'], '13'); }); test('`content-length` header with form body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({form: {foo: 'bar'}}); const headers = JSON.parse(body); t.is(headers['content-length'], '7'); }); test('`content-length` header with Buffer body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: Buffer.from('wow')}); const headers = JSON.parse(body); t.is(headers['content-length'], '3'); }); test('`content-length` header with Stream body', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({body: toReadableStream('wow')}); const headers = JSON.parse(body); t.is(headers['transfer-encoding'], 'chunked', 'likely failed to get headers at all'); t.is(headers['content-length'], undefined); }); test('`content-length` header is not overriden', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ body: 'wow', headers: { 'content-length': '10' } }); const headers = JSON.parse(body); t.is(headers['content-length'], '10'); }); test('`content-length` header is present when using custom content-type', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ json: {foo: 'bar'}, headers: { 'content-type': 'custom' } }); const headers = JSON.parse(body); t.is(headers['content-length'], '13'); }); test('`content-length` header disabled for chunked transfer-encoding', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body} = await got.post({ body: '3\r\nwow\r\n0\r\n', headers: { 'transfer-encoding': 'chunked' } }); const headers = JSON.parse(body); t.is(headers['transfer-encoding'], 'chunked', 'likely failed to get headers at all'); t.is(headers['content-length'], undefined); }); test('`content-type` header is not overriden when object in `options.body`', withServer, async (t, server, got) => { server.post('/', echoHeaders); const {body: headers} = await got.post>({ headers: { 'content-type': 'doge' }, json: { such: 'wow' }, responseType: 'json' }); t.is(headers['content-type'], 'doge'); }); test('throws when form body is not a plain object or array', async t => { // @ts-expect-error Manual test await t.throwsAsync(got.post('https://example.com', {form: 'such=wow'}), { message: 'The `form` option must be an Object' }); }); // See https://github.com/sindresorhus/got/issues/897 test('the `json` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const {body} = await got.post<{context: {foo: true}}>({ json: { context: { foo: true } }, responseType: 'json' }); t.true('context' in body); t.true(body.context.foo); }); test('the `body` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const buffer = Buffer.from('Hello, Got!'); await got.post({ body: buffer, hooks: { beforeRequest: [ options => { t.is(options.body, buffer); } ] } }); }); test('the `form` payload is not touched', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const object = { foo: 'bar' }; await got.post({ form: object, hooks: { beforeRequest: [ options => { t.is(options.form, object); } ] } }); }); test('DELETE method sends plain objects as JSON', withServer, async (t, server, got) => { server.delete('/', defaultEndpoint); const {body} = await got.delete({ json: {such: 'wow'}, responseType: 'json' }); t.deepEqual(body, {such: 'wow'}); }); test('catches body errors before calling pipeline() - promise', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(got.post({ body: fs.createReadStream('./file-that-does-not-exist.txt') }), { message: /ENOENT: no such file or directory/ }); // Wait for unhandled errors await delay(100); }); test('catches body errors before calling pipeline() - stream', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); await t.throwsAsync(getStream(got.stream.post({ body: fs.createReadStream('./file-that-does-not-exist.txt') })), { message: /ENOENT: no such file or directory/ }); // Wait for unhandled errors await delay(100); }); test('body - file read stream', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const fullPath = path.resolve('test/fixtures/ok'); const toSend = await getStream(fs.createReadStream(fullPath)); const body = await got.post({ body: fs.createReadStream(fullPath) }).text(); t.is(toSend, body); }); test('body - file read stream, wait for `ready` event', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const fullPath = path.resolve('test/fixtures/ok'); const toSend = await getStream(fs.createReadStream(fullPath)); const ifStream = fs.createReadStream(fullPath); await pEvent(ifStream, 'ready'); const body = await got.post({ body: ifStream }).text(); t.is(toSend, body); }); test('throws on upload error', withServer, async (t, server, got) => { server.post('/', defaultEndpoint); const body = new stream.PassThrough(); const message = 'oh no'; await t.throwsAsync(getStream(got.stream.post({ body, hooks: { beforeRequest: [ () => { process.nextTick(() => { body.destroy(new Error(message)); }); } ] } })), { instanceOf: UploadError, code: 'ERR_UPLOAD', message }); }); got-11.8.5/test/progress.ts000066400000000000000000000124611424347352000156020ustar00rootroot00000000000000import {promisify} from 'util'; import stream = require('stream'); import fs = require('fs'); import SlowStream = require('slow-stream'); import toReadableStream = require('to-readable-stream'); import getStream = require('get-stream'); import FormData = require('form-data'); import tempy = require('tempy'); import is from '@sindresorhus/is'; import test, {ExecutionContext} from 'ava'; import {Handler} from 'express'; import {Progress} from '../source'; import withServer from './helpers/with-server'; const checkEvents = (t: ExecutionContext, events: Progress[], bodySize?: number) => { t.true(events.length >= 2); let lastEvent = events.shift()!; if (!is.number(bodySize)) { t.is(lastEvent.percent, 0); } for (const [index, event] of events.entries()) { const isLastEvent = index === events.length - 1; if (is.number(bodySize)) { t.is(event.percent, event.transferred / bodySize); t.true(event.percent > lastEvent.percent); t.true(event.transferred > lastEvent.transferred); } else if (isLastEvent) { t.is(event.percent, 1); t.is(event.transferred, lastEvent.transferred); t.is(event.total, event.transferred); } else { t.is(event.percent, 0); t.true(event.transferred > lastEvent.transferred); } lastEvent = event; } }; const file = Buffer.alloc(1024 * 1024 * 2); const downloadEndpoint: Handler = (_request, response) => { response.setHeader('content-length', file.length); stream.pipeline( toReadableStream(file), new SlowStream({maxWriteInterval: 50}), response, () => { response.end(); } ); }; const noTotalEndpoint: Handler = (_request, response) => { response.write('hello'); response.end(); }; const uploadEndpoint: Handler = (request, response) => { stream.pipeline( request, new SlowStream({maxWriteInterval: 100}), () => { response.end(); } ); }; test('download progress', withServer, async (t, server, got) => { server.get('/', downloadEndpoint); const events: Progress[] = []; const {body} = await got({responseType: 'buffer'}) .on('downloadProgress', event => events.push(event)); checkEvents(t, events, body.length); }); test('download progress - missing total size', withServer, async (t, server, got) => { server.get('/', noTotalEndpoint); const events: Progress[] = []; await got('').on('downloadProgress', (event: Progress) => events.push(event)); t.is(events[0].total, undefined); checkEvents(t, events); }); test('download progress - stream', withServer, async (t, server, got) => { server.get('/', downloadEndpoint); const events: Progress[] = []; const stream = got.stream({responseType: 'buffer'}) .on('downloadProgress', event => events.push(event)); await getStream(stream); checkEvents(t, events, file.length); }); test('upload progress - file', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const events: Progress[] = []; await got.post({body: file}).on('uploadProgress', (event: Progress) => events.push(event)); checkEvents(t, events, file.length); }); test('upload progress - file stream', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const path = tempy.file(); fs.writeFileSync(path, file); const events: Progress[] = []; await got.post({body: fs.createReadStream(path)}) .on('uploadProgress', (event: Progress) => events.push(event)); checkEvents(t, events, file.length); }); test('upload progress - form data', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const events: Progress[] = []; const body = new FormData(); body.append('key', 'value'); body.append('file', file); const size = await promisify(body.getLength.bind(body))(); await got.post({body}).on('uploadProgress', (event: Progress) => events.push(event)); checkEvents(t, events, size); }); test('upload progress - json', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const body = JSON.stringify({key: 'value'}); const size = Buffer.byteLength(body); const events: Progress[] = []; await got.post({body}).on('uploadProgress', (event: Progress) => events.push(event)); checkEvents(t, events, size); }); test('upload progress - stream with known body size', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const events: Progress[] = []; const options = { headers: {'content-length': file.length.toString()} }; const request = got.stream.post(options) .on('uploadProgress', event => events.push(event)); await getStream( stream.pipeline(toReadableStream(file), request, () => {}) ); checkEvents(t, events, file.length); }); test('upload progress - stream with unknown body size', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const events: Progress[] = []; const request = got.stream.post('') .on('uploadProgress', event => events.push(event)); await getStream( stream.pipeline(toReadableStream(file), request, () => {}) ); t.is(events[0].total, undefined); checkEvents(t, events); }); test('upload progress - no body', withServer, async (t, server, got) => { server.post('/', uploadEndpoint); const events: Progress[] = []; await got.post('').on('uploadProgress', (event: Progress) => events.push(event)); t.deepEqual(events, [ { percent: 0, transferred: 0, total: undefined }, { percent: 1, transferred: 0, total: 0 } ]); }); got-11.8.5/test/promise.ts000066400000000000000000000047161424347352000154200ustar00rootroot00000000000000import {ReadStream} from 'fs'; import {ClientRequest, IncomingMessage} from 'http'; import test from 'ava'; import {Response, CancelError} from '../source'; import withServer from './helpers/with-server'; test('emits request event as promise', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 200; response.end('null'); }); await got('').json().on('request', (request: ClientRequest) => { t.true(request instanceof ClientRequest); }); }); test('emits response event as promise', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 200; response.end('null'); }); await got('').json().on('response', (response: Response) => { t.true(response instanceof IncomingMessage); t.true(response.readable); t.is(response.statusCode, 200); t.is(response.ip, '127.0.0.1'); }); }); test('returns buffer on compressed response', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('content-encoding', 'gzip'); response.end(); }); const {body} = await got({decompress: false}); t.true(Buffer.isBuffer(body)); }); test('no unhandled `The server aborted pending request` rejection', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 503; response.write('asdf'); setTimeout(() => { response.end(); }, 100); }); await t.throwsAsync(got('')); }); test('promise.json() can be called before a file stream body is open', withServer, async (t, server, got) => { server.post('/', (request, response) => { request.resume(); request.once('end', () => { response.end('""'); }); }); // @ts-expect-error @types/node has wrong types. const body = new ReadStream('', { fs: { open: () => {}, read: () => {}, close: () => {} } }); const promise = got({body}); const checks = [ t.throwsAsync(promise, { instanceOf: CancelError, code: 'ERR_CANCELED' }), t.throwsAsync(promise.json(), { instanceOf: CancelError, code: 'ERR_CANCELED' }) ]; promise.cancel(); await Promise.all(checks); }); test('promise.json() does not fail when server returns an error and throwHttpErrors is disabled', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 400; response.end('{}'); }); const promise = got('', {throwHttpErrors: false}); await t.notThrowsAsync(promise.json()); }); got-11.8.5/test/redirects.ts000066400000000000000000000320551424347352000157230ustar00rootroot00000000000000import test from 'ava'; import {Handler} from 'express'; import nock = require('nock'); import got, {MaxRedirectsError, RequestError} from '../source'; import withServer, {withHttpsServer} from './helpers/with-server'; const reachedHandler: Handler = (_request, response) => { const body = 'reached'; response.writeHead(200, { 'content-length': body.length }); response.end(body); }; const finiteHandler: Handler = (_request, response) => { response.writeHead(302, { location: '/' }); response.end(); }; const relativeHandler: Handler = (_request, response) => { response.writeHead(302, { location: '/' }); response.end(); }; test('follows redirect', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/finite', finiteHandler); const {body, redirectUrls} = await got('finite'); t.is(body, 'reached'); t.deepEqual(redirectUrls, [`${server.url}/`]); }); test('follows 307, 308 redirect', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/temporary', (_request, response) => { response.writeHead(307, { location: '/' }); response.end(); }); server.get('/permanent', (_request, response) => { response.writeHead(308, { location: '/' }); response.end(); }); const temporaryBody = (await got('temporary')).body; t.is(temporaryBody, 'reached'); const permBody = (await got('permanent')).body; t.is(permBody, 'reached'); }); test('does not follow redirect when disabled', withServer, async (t, server, got) => { server.get('/', finiteHandler); t.is((await got({followRedirect: false})).statusCode, 302); }); test('relative redirect works', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/relative', relativeHandler); t.is((await got('relative')).body, 'reached'); }); test('throws on endless redirects - default behavior', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: server.url }); response.end(); }); const error = await t.throwsAsync(got(''), {message: 'Redirected 10 times. Aborting.'}); t.deepEqual(error.response.redirectUrls, new Array(10).fill(`${server.url}/`)); t.is(error.code, 'ERR_TOO_MANY_REDIRECTS'); }); test('custom `maxRedirects` option', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: server.url }); response.end(); }); const error = await t.throwsAsync(got('', {maxRedirects: 5}), {message: 'Redirected 5 times. Aborting.'}); t.deepEqual(error.response.redirectUrls, new Array(5).fill(`${server.url}/`)); t.is(error.code, 'ERR_TOO_MANY_REDIRECTS'); }); test('searchParams are not breaking redirects', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/relativeSearchParam', (request, response) => { t.is(request.query.bang, '1'); response.writeHead(302, { location: '/' }); response.end(); }); t.is((await got('relativeSearchParam', {searchParams: 'bang=1'})).body, 'reached'); }); test('redirects GET and HEAD requests', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(308, { location: '/' }); response.end(); }); await t.throwsAsync(got.get(''), { instanceOf: got.MaxRedirectsError, code: 'ERR_TOO_MANY_REDIRECTS' }); }); test('redirects POST requests', withServer, async (t, server, got) => { server.post('/', (_request, response) => { response.writeHead(308, { location: '/' }); response.end(); }); await t.throwsAsync(got.post({body: 'wow'}), { instanceOf: got.MaxRedirectsError, code: 'ERR_TOO_MANY_REDIRECTS' }); }); test('redirects on 303 if GET or HEAD', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.head('/seeOther', (_request, response) => { response.writeHead(303, { location: '/' }); response.end(); }); const {url, headers, request} = await got.head('seeOther'); t.is(url, `${server.url}/`); t.is(headers['content-length'], 'reached'.length.toString()); t.is(request.options.method, 'HEAD'); }); test('removes body on GET redirect', withServer, async (t, server, got) => { server.get('/', (request, response) => request.pipe(response)); server.post('/seeOther', (_request, response) => { response.writeHead(303, { location: '/' }); response.end(); }); const {headers, body} = await got.post('seeOther', {body: 'hello'}); t.is(body, ''); t.is(headers['content-length'], '0'); }); test('redirects on 303 response even on post, put, delete', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.post('/seeOther', (_request, response) => { response.writeHead(303, { location: '/' }); response.end(); }); const {url, body} = await got.post('seeOther', {body: 'wow'}); t.is(url, `${server.url}/`); t.is(body, 'reached'); }); test('redirects from http to https work', withServer, async (t, serverHttp) => { await withHttpsServer()(t, async (t, serverHttps, got) => { serverHttp.get('/', (_request, response) => { response.end('http'); }); serverHttps.get('/', (_request, response) => { response.end('https'); }); serverHttp.get('/httpToHttps', (_request, response) => { response.writeHead(302, { location: serverHttps.url }); response.end(); }); t.is((await got('httpToHttps', { prefixUrl: serverHttp.url })).body, 'https'); }); }); test('redirects from https to http work', withHttpsServer(), async (t, serverHttps, got) => { await withServer(t, async (t, serverHttp) => { serverHttp.get('/', (_request, response) => { response.end('http'); }); serverHttps.get('/', (_request, response) => { response.end('https'); }); serverHttps.get('/httpsToHttp', (_request, response) => { response.writeHead(302, { location: serverHttp.url }); response.end(); }); t.is((await got('httpsToHttp', { prefixUrl: serverHttps.url })).body, 'http'); }); }); test('redirects works with lowercase method', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/relative', relativeHandler); const {body} = await got('relative', {method: 'head'}); t.is(body, ''); }); test('redirect response contains new url', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/finite', finiteHandler); const {url} = await got('finite'); t.is(url, `${server.url}/`); }); test('redirect response contains old url', withServer, async (t, server, got) => { server.get('/', reachedHandler); server.get('/finite', finiteHandler); const {requestUrl} = await got('finite'); t.is(requestUrl, `${server.url}/finite`); }); test('redirect response contains UTF-8 with binary encoding', withServer, async (t, server, got) => { server.get('/utf8-url-%C3%A1%C3%A9', reachedHandler); server.get('/redirect-with-utf8-binary', (_request, response) => { response.writeHead(302, { location: Buffer.from((new URL('/utf8-url-áé', server.url)).toString(), 'utf8').toString('binary') }); response.end(); }); t.is((await got('redirect-with-utf8-binary')).body, 'reached'); }); test('redirect response contains UTF-8 with URI encoding', withServer, async (t, server, got) => { server.get('/', (request, response) => { t.is(request.query.test, 'it’s ok'); response.end('reached'); }); server.get('/redirect-with-uri-encoded-location', (_request, response) => { response.writeHead(302, { location: new URL('/?test=it’s+ok', server.url).toString() }); response.end(); }); t.is((await got('redirect-with-uri-encoded-location')).body, 'reached'); }); test('throws on malformed redirect URI', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: '/%D8' }); response.end(); }); await t.throwsAsync(got(''), { message: 'URI malformed' }); }); test('throws on invalid redirect URL', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: 'http://' }); response.end(); }); await t.throwsAsync(got(''), { code: 'ERR_INVALID_URL' }); }); test('port is reset on redirect', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(307, { location: 'http://localhost' }); response.end(); }); nock('http://localhost').get('/').reply(200, 'ok'); const {body} = await got(''); t.is(body, 'ok'); }); test('body is reset on GET redirect', withServer, async (t, server, got) => { server.post('/', (_request, response) => { response.writeHead(303, { location: '/' }); response.end(); }); server.get('/', (_request, response) => { response.end(); }); await got.post('', { body: 'foobar', hooks: { beforeRedirect: [ options => { t.is(options.body, undefined); } ] } }); await got.post('', { json: {foo: 'bar'}, hooks: { beforeRedirect: [ options => { t.is(options.body, undefined); } ] } }); await got.post('', { form: {foo: 'bar'}, hooks: { beforeRedirect: [ options => { t.is(options.body, undefined); } ] } }); }); test('body is passed on POST redirect', withServer, async (t, server, got) => { server.post('/redirect', (_request, response) => { response.writeHead(302, { location: '/' }); response.end(); }); server.post('/', (request, response) => { request.pipe(response); }); const {body} = await got.post('redirect', { body: 'foobar', hooks: { beforeRedirect: [ options => { t.is(options.body, 'foobar'); } ] } }); t.is(body, 'foobar'); }); test('method rewriting can be turned off', withServer, async (t, server, got) => { server.post('/redirect', (_request, response) => { response.writeHead(302, { location: '/' }); response.end(); }); server.get('/', (_request, response) => { response.end(); }); const {body} = await got.post('redirect', { body: 'foobar', methodRewriting: false, hooks: { beforeRedirect: [ options => { t.is(options.body, undefined); } ] } }); t.is(body, ''); }); test('clears username and password when redirecting to a different hostname', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: 'https://httpbin.org/anything' }); response.end(); }); const {headers} = await got('', { username: 'hello', password: 'world' }).json(); t.is(headers.Authorization, undefined); }); test('clears the authorization header when redirecting to a different hostname', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.writeHead(302, { location: 'https://httpbin.org/anything' }); response.end(); }); const {headers} = await got('', { headers: { authorization: 'Basic aGVsbG86d29ybGQ=' } }).json(); t.is(headers.Authorization, undefined); }); test('preserves userinfo on redirect to the same origin', withServer, async (t, server) => { server.get('/redirect', (_request, response) => { response.writeHead(303, { location: `http://localhost:${server.port}/` }); response.end(); }); server.get('/', (request, response) => { t.is(request.headers.authorization, 'Basic aGVsbG86d29ybGQ='); response.end(); }); await got(`http://hello:world@localhost:${server.port}/redirect`); }); test('clears the host header when redirecting to a different hostname', async t => { nock('https://testweb.com').get('/redirect').reply(302, undefined, {location: 'https://webtest.com/'}); nock('https://webtest.com').get('/').reply(function (_uri, _body) { return [200, this.req.getHeader('host')]; }); const resp = await got('https://testweb.com/redirect', {headers: {host: 'wrongsite.com'}}); t.is(resp.body, 'webtest.com'); }); test('correct port on redirect', withServer, async (t, server1, got) => { await withServer(t, async (t, server2) => { server1.get('/redirect', (_request, response) => { response.redirect(`http://${server2.hostname}:${server2.port}/`); }); server1.get('/', (_request, response) => { response.end('SERVER1'); }); server2.get('/', (_request, response) => { response.end('SERVER2'); }); const response = await got({ protocol: 'http:', hostname: server1.hostname, port: server1.port, pathname: '/redirect' }); t.is(response.body, 'SERVER2'); }); }); const unixProtocol: Handler = (_request, response) => { response.writeHead(302, { location: 'unix:/var/run/docker.sock:/containers/json' }); response.end(); }; const unixHostname: Handler = (_request, response) => { response.writeHead(302, { location: 'http://unix:/var/run/docker.sock:/containers/json' }); response.end(); }; test('cannot redirect to unix protocol', withServer, async (t, server, got) => { server.get('/protocol', unixProtocol); server.get('/hostname', unixHostname); await t.throwsAsync(got('protocol'), { message: 'Cannot redirect to UNIX socket', instanceOf: RequestError }); await t.throwsAsync(got('hostname'), { message: 'Cannot redirect to UNIX socket', instanceOf: RequestError }); }); got-11.8.5/test/response-parse.ts000066400000000000000000000165671424347352000167170ustar00rootroot00000000000000import test from 'ava'; import {Handler} from 'express'; import getStream = require('get-stream'); import {HTTPError, ParseError} from '../source'; import withServer from './helpers/with-server'; const dog = {data: 'dog'}; const jsonResponse = JSON.stringify(dog); const defaultHandler: Handler = (_request, response) => { response.end(jsonResponse); }; test('`options.resolveBodyOnly` works', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual(await got>({responseType: 'json', resolveBodyOnly: true}), dog); }); test('`options.resolveBodyOnly` combined with `options.throwHttpErrors`', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 404; response.end('/'); }); t.is(await got({resolveBodyOnly: true, throwHttpErrors: false}), '/'); }); test('JSON response', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual((await got({responseType: 'json'})).body, dog); }); test('Buffer response', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual((await got({responseType: 'buffer'})).body, Buffer.from(jsonResponse)); }); test('Text response', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.is((await got({responseType: 'text'})).body, jsonResponse); }); test('Text response #2', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.is((await got({responseType: undefined})).body, jsonResponse); }); test('JSON response - promise.json()', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual(await got('').json(), dog); }); test('Buffer response - promise.buffer()', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual(await got('').buffer(), Buffer.from(jsonResponse)); }); test('Text response - promise.text()', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.is(await got('').text(), jsonResponse); }); test('Text response - promise.json().text()', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.is(await got('').json().text(), jsonResponse); }); test('works if promise has been already resolved', withServer, async (t, server, got) => { server.get('/', defaultHandler); const promise = got('').text(); t.is(await promise, jsonResponse); t.deepEqual(await promise.json(), dog); }); test('throws an error on invalid response type', withServer, async (t, server, got) => { server.get('/', defaultHandler); // @ts-expect-error Error tests const error = await t.throwsAsync(got({responseType: 'invalid'})); t.regex(error.message, /^Unknown body type 'invalid'/); t.true(error.message.includes(error.options.url.hostname)); t.is(error.options.url.pathname, '/'); t.is(error.code, 'ERR_BODY_PARSE_FAILURE'); }); test('wraps parsing errors', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('/'); }); const error = await t.throwsAsync(got({responseType: 'json'}), {instanceOf: got.ParseError}); t.true(error.message.includes(error.options.url.hostname)); t.is(error.options.url.pathname, '/'); t.is(error.code, 'ERR_BODY_PARSE_FAILURE'); }); test('parses non-200 responses', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end(jsonResponse); }); const error = await t.throwsAsync(got({responseType: 'json', retry: 0}), {instanceOf: HTTPError}); t.deepEqual(error.response.body, dog); }); test('ignores errors on invalid non-200 responses', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end('Internal error'); }); const error = await t.throwsAsync(got({responseType: 'json', retry: 0}), { instanceOf: got.HTTPError, message: 'Response code 500 (Internal Server Error)' }); t.is(error.response.body, 'Internal error'); t.is(error.options.url.pathname, '/'); }); test('parse errors have `response` property', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('/'); }); const error = await t.throwsAsync(got({responseType: 'json'}), {instanceOf: ParseError}); t.is(error.response.statusCode, 200); t.is(error.response.body, '/'); t.is(error.code, 'ERR_BODY_PARSE_FAILURE'); }); test('sets correct headers', withServer, async (t, server, got) => { server.post('/', (request, response) => { response.end(JSON.stringify(request.headers)); }); const {body: headers} = await got.post>({responseType: 'json', json: {}}); t.is(headers['content-type'], 'application/json'); t.is(headers.accept, 'application/json'); }); test('doesn\'t throw on 204 No Content', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 204; response.end(); }); const body = await got('').json(); t.is(body, ''); }); test('doesn\'t throw on empty bodies', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 200; response.end(); }); const body = await got('').json(); t.is(body, ''); }); test('.buffer() returns binary content', withServer, async (t, server, got) => { const body = Buffer.from('89504E470D0A1A0A0000000D49484452', 'hex'); server.get('/', (_request, response) => { response.end(body); }); const buffer = await got('').buffer(); t.is(Buffer.compare(buffer, body), 0); }); test('shortcuts throw ParseErrors', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('not a json'); }); await t.throwsAsync(got('').json(), { instanceOf: ParseError, code: 'ERR_BODY_PARSE_FAILURE', message: /^Unexpected token o in JSON at position 1 in/ }); }); test('shortcuts result properly when retrying in afterResponse', withServer, async (t, server, got) => { const nasty = JSON.stringify({hello: 'nasty'}); const proper = JSON.stringify({hello: 'world'}); server.get('/', (request, response) => { if (request.headers.token === 'unicorn') { response.end(proper); } else { response.statusCode = 401; response.end(nasty); } }); const promise = got({ hooks: { afterResponse: [ (response, retryWithMergedOptions) => { if (response.statusCode === 401) { return retryWithMergedOptions({ headers: { token: 'unicorn' } }); } return response; } ] } }); const json = await promise.json<{hello: string}>(); const text = await promise.text(); const buffer = await promise.buffer(); t.is(json.hello, 'world'); t.is(text, proper); t.is(buffer.compare(Buffer.from(proper)), 0); }); test('responseType is optional when using template', withServer, async (t, server, got) => { const data = {hello: 'world'}; server.post('/', async (request, response) => { response.end(await getStream(request)); }); const jsonClient = got.extend({responseType: 'json'}); const {body} = await jsonClient.post('', {json: data}); t.deepEqual(body, data); }); test('JSON response custom parser', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.deepEqual((await got({ responseType: 'json', parseJson: text => ({...JSON.parse(text), custom: 'parser'}) })).body, {...dog, custom: 'parser'}); }); got-11.8.5/test/retry.ts000066400000000000000000000265651424347352000151150ustar00rootroot00000000000000import {EventEmitter} from 'events'; import {PassThrough as PassThroughStream} from 'stream'; import {Socket} from 'net'; import http = require('http'); import test from 'ava'; import is from '@sindresorhus/is'; import {Handler} from 'express'; import getStream = require('get-stream'); import pEvent = require('p-event'); import got, {HTTPError} from '../source'; import withServer from './helpers/with-server'; const retryAfterOn413 = 2; const socketTimeout = 300; const handler413: Handler = (_request, response) => { response.writeHead(413, { 'Retry-After': retryAfterOn413 }); response.end(); }; const createSocketTimeoutStream = (): http.ClientRequest => { const stream = new PassThroughStream(); // @ts-expect-error Mocking the behaviour of a ClientRequest stream.setTimeout = (ms, callback) => { process.nextTick(callback); }; // @ts-expect-error Mocking the behaviour of a ClientRequest stream.abort = () => {}; stream.resume(); return stream as unknown as http.ClientRequest; }; test('works on timeout', withServer, async (t, server, got) => { let knocks = 0; server.get('/', (_request, response) => { response.end('who`s there?'); }); t.is((await got({ timeout: { socket: socketTimeout }, request: (...args: [ string | URL | http.RequestOptions, (http.RequestOptions | ((response: http.IncomingMessage) => void))?, ((response: http.IncomingMessage) => void)? ]) => { if (knocks === 1) { // @ts-expect-error Overload error return http.request(...args); } knocks++; return createSocketTimeoutStream(); } })).body, 'who`s there?'); }); test('retry function gets iteration count', withServer, async (t, server, got) => { let knocks = 0; server.get('/', (_request, response) => { if (knocks++ === 1) { response.end('who`s there?'); return; } response.statusCode = 500; response.end(); }); await got({ retry: { calculateDelay: ({attemptCount}) => { t.true(is.number(attemptCount)); return attemptCount < 2 ? 1 : 0; } } }); }); test('setting to `0` disables retrying', async t => { await t.throwsAsync(got('https://example.com', { timeout: {socket: socketTimeout}, retry: { calculateDelay: ({attemptCount}) => { t.is(attemptCount, 1); return 0; } }, request: () => { return createSocketTimeoutStream(); } }), { instanceOf: got.TimeoutError, message: `Timeout awaiting 'socket' for ${socketTimeout}ms` }); }); test('custom retries', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end(); }); let hasTried = false; const error = await t.throwsAsync(got({ throwHttpErrors: true, retry: { calculateDelay: ({attemptCount}) => { if (attemptCount === 1) { hasTried = true; return 1; } return 0; }, methods: [ 'GET' ], statusCodes: [ 500 ] } })); t.is(error.response.statusCode, 500); t.true(hasTried); }); test('custom retries async', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end(); }); let hasTried = false; const error = await t.throwsAsync(got({ throwHttpErrors: true, retry: { calculateDelay: async ({attemptCount}) => { await new Promise(resolve => { setTimeout(resolve, 1000); }); if (attemptCount === 1) { hasTried = true; return 1; } return 0; }, methods: [ 'GET' ], statusCodes: [ 500 ] } })); t.is(error.response.statusCode, 500); t.true(hasTried); }); test('custom error codes', async t => { const errorCode = 'OH_SNAP'; const error = await t.throwsAsync(got('https://example.com', { request: () => { const emitter = new EventEmitter() as http.ClientRequest; emitter.abort = () => {}; // @ts-expect-error emitter.end = () => {}; // @ts-expect-error emitter.destroy = () => {}; const error = new Error('Snap!'); (error as Error & {code: typeof errorCode}).code = errorCode; setTimeout(() => { emitter.emit('error', error); }); return emitter; }, retry: { calculateDelay: ({error}) => { t.is(error.code, errorCode); return 0; }, methods: [ 'GET' ], errorCodes: [ errorCode ] } })); t.is(error.code, errorCode); }); test('respects 413 Retry-After', withServer, async (t, server, got) => { let lastTried413access = Date.now(); server.get('/', (_request, response) => { response.writeHead(413, { 'Retry-After': retryAfterOn413 }); response.end((Date.now() - lastTried413access).toString()); lastTried413access = Date.now(); }); const {statusCode, body} = await got({ throwHttpErrors: false, retry: 1 }); t.is(statusCode, 413); t.true(Number(body) >= retryAfterOn413 * 1000); }); test('respects 413 Retry-After with RFC-1123 timestamp', withServer, async (t, server, got) => { let lastTried413TimestampAccess: string; server.get('/', (_request, response) => { const date = (new Date(Date.now() + (retryAfterOn413 * 1000))).toUTCString(); response.writeHead(413, { 'Retry-After': date }); response.end(lastTried413TimestampAccess); lastTried413TimestampAccess = date; }); const {statusCode, body} = await got({ throwHttpErrors: false, retry: 1 }); t.is(statusCode, 413); t.true(Date.now() >= Date.parse(body)); }); test('doesn\'t retry on 413 with empty statusCodes and methods', withServer, async (t, server, got) => { server.get('/', handler413); const {statusCode, retryCount} = await got({ throwHttpErrors: false, retry: { limit: 1, statusCodes: [], methods: [] } }); t.is(statusCode, 413); t.is(retryCount, 0); }); test('doesn\'t retry on 413 with empty methods', withServer, async (t, server, got) => { server.get('/', handler413); const {statusCode, retryCount} = await got({ throwHttpErrors: false, retry: { limit: 1, statusCodes: [413], methods: [] } }); t.is(statusCode, 413); t.is(retryCount, 0); }); test('doesn\'t retry on 413 without Retry-After header', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 413; response.end(); }); const {retryCount} = await got({ throwHttpErrors: false }); t.is(retryCount, 0); }); test('retries on 503 without Retry-After header', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 503; response.end(); }); const {retryCount} = await got({ throwHttpErrors: false, retry: 1 }); t.is(retryCount, 1); }); test('doesn\'t retry on streams', withServer, async (t, server, got) => { server.get('/', () => {}); // @ts-expect-error Error tests const stream = got.stream({ timeout: 1, retry: { retries: () => { t.fail('Retries on streams'); } } }); await t.throwsAsync(pEvent(stream, 'response')); }); test('doesn\'t retry if Retry-After header is greater than maxRetryAfter', withServer, async (t, server, got) => { server.get('/', handler413); const {retryCount} = await got({ retry: {maxRetryAfter: 1000}, throwHttpErrors: false }); t.is(retryCount, 0); }); test('doesn\'t retry when set to 0', withServer, async (t, server, got) => { server.get('/', handler413); const {statusCode, retryCount} = await got({ throwHttpErrors: false, retry: 0 }); t.is(statusCode, 413); t.is(retryCount, 0); }); test('works when defaults.options.retry is a number', withServer, async (t, server, got) => { server.get('/', handler413); const instance = got.extend({ retry: 2 }); const {retryCount} = await instance({ throwHttpErrors: false }); t.is(retryCount, 2); }); test('retry function can throw', withServer, async (t, server, got) => { server.get('/', handler413); const error = 'Simple error'; await t.throwsAsync(got({ retry: { calculateDelay: () => { throw new Error(error); } } }), {message: error}); }); test('does not retry on POST', withServer, async (t, server, got) => { server.post('/', () => {}); await t.throwsAsync(got.post({ timeout: 200, hooks: { beforeRetry: [ () => { t.fail('Retries on POST requests'); } ] } }), {instanceOf: got.TimeoutError}); }); test('does not break on redirect', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end(); }); let tries = 0; server.get('/redirect', (_request, response) => { tries++; response.writeHead(302, { location: '/' }); response.end(); }); await t.throwsAsync(got('redirect'), {message: 'Response code 500 (Internal Server Error)'}); t.is(tries, 1); }); test('does not destroy the socket on HTTP error', withServer, async (t, server, got) => { let returnServerError = true; server.get('/', (_request, response) => { if (returnServerError) { response.statusCode = 500; returnServerError = false; } response.end(); }); const sockets: Socket[] = []; const agent = new http.Agent({ keepAlive: true }); await got('', { agent: { http: agent } }).on('request', request => { sockets.push(request.socket!); }); t.is(sockets.length, 2); t.is(sockets[0], sockets[1]); agent.destroy(); }); test('can retry a Got stream', withServer, async (t, server, got) => { let returnServerError = true; server.get('/', (_request, response) => { if (returnServerError) { response.statusCode = 500; response.end('not ok'); returnServerError = false; return; } response.end('ok'); }); let globalRetryCount = 0; const responseStreamPromise = new Promise((resolve, reject) => { let writeStream: PassThroughStream; const fn = (retryCount = 0) => { const stream = got.stream(''); stream.retryCount = retryCount; globalRetryCount = retryCount; if (writeStream) { writeStream.destroy(); } writeStream = new PassThroughStream(); stream.pipe(writeStream); stream.once('retry', fn); stream.once('error', reject); stream.once('end', () => { resolve(writeStream); }); }; fn(); }); const responseStream = await responseStreamPromise; const data = await getStream(responseStream); t.is(data, 'ok'); t.is(globalRetryCount, 1); }); test('throws when cannot retry a Got stream', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.statusCode = 500; response.end('not ok'); }); let globalRetryCount = 0; const streamPromise = new Promise((resolve, reject) => { const fn = (retryCount = 0) => { const stream = got.stream(''); stream.retryCount = retryCount; globalRetryCount = retryCount; stream.resume(); stream.once('retry', fn); stream.once('data', () => { stream.destroy(new Error('data event has been emitted')); }); stream.once('error', reject); stream.once('end', resolve); }; fn(); }); const error = await t.throwsAsync(streamPromise, { instanceOf: HTTPError }); t.is(error.response.statusCode, 500); t.is(error.response.body, 'not ok'); t.is(globalRetryCount, 2); }); test('promise does not retry when body is a stream', withServer, async (t, server, got) => { server.post('/', (_request, response) => { response.statusCode = 500; response.end('not ok'); }); const body = new PassThroughStream(); body.end('hello'); const response = await got.post({ retry: { methods: ['POST'] }, body, throwHttpErrors: false }); t.is(response.retryCount, 0); }); got-11.8.5/test/stream.ts000066400000000000000000000271151424347352000152330ustar00rootroot00000000000000import {promisify} from 'util'; import fs = require('fs'); import {PassThrough as PassThroughStream} from 'stream'; import stream = require('stream'); import test from 'ava'; import {Handler} from 'express'; import toReadableStream = require('to-readable-stream'); import getStream = require('get-stream'); import pEvent = require('p-event'); import FormData = require('form-data'); import is from '@sindresorhus/is'; import got, {RequestError} from '../source'; import withServer from './helpers/with-server'; const pStreamPipeline = promisify(stream.pipeline); const defaultHandler: Handler = (_request, response) => { response.writeHead(200, { unicorn: 'rainbow', 'content-encoding': 'gzip' }); response.end(Buffer.from('H4sIAAAAAAAA/8vPBgBH3dx5AgAAAA==', 'base64')); // 'ok' }; const redirectHandler: Handler = (_request, response) => { response.writeHead(302, { location: '/' }); response.end(); }; const postHandler: Handler = async (request, response) => { await pStreamPipeline(request, response); }; const errorHandler: Handler = (_request, response) => { response.statusCode = 404; response.end(); }; const headersHandler: Handler = (request, response) => { response.end(JSON.stringify(request.headers)); }; const infiniteHandler: Handler = (_request, response) => { response.write('foobar'); }; test('`options.responseType` is ignored', withServer, async (t, server, got) => { server.get('/', defaultHandler); await t.notThrowsAsync(getStream(got.stream({responseType: 'json'}))); }); test('returns readable stream', withServer, async (t, server, got) => { server.get('/', defaultHandler); const data = await getStream(got.stream('')); t.is(data, 'ok'); }); test('returns writeable stream', withServer, async (t, server, got) => { server.post('/', postHandler); const stream = got.stream.post(''); const promise = getStream(stream); stream.end('wow'); t.is(await promise, 'wow'); }); test('throws on write if body is specified', withServer, (t, server, got) => { server.post('/', postHandler); const streams = [ got.stream.post({body: 'wow'}), got.stream.post({json: {}}), got.stream.post({form: {}}) ]; for (const stream of streams) { t.throws(() => { stream.end('wow'); }, { message: 'The payload has been already provided' }); stream.destroy(); } }); test('does not throw if using stream and passing a json option', withServer, async (t, server, got) => { server.post('/', postHandler); await t.notThrowsAsync(getStream(got.stream.post({json: {}}))); }); test('does not throw if using stream and passing a form option', withServer, async (t, server, got) => { server.post('/', postHandler); await t.notThrowsAsync(getStream(got.stream.post({form: {}}))); }); test('throws on write if no payload method is present', withServer, (t, server, got) => { server.post('/', postHandler); const stream = got.stream.get(''); t.throws(() => { stream.end('wow'); }, { message: 'The payload has been already provided' }); stream.destroy(); }); test('has request event', withServer, async (t, server, got) => { server.get('/', defaultHandler); const stream = got.stream(''); const request = await pEvent(stream, 'request'); t.truthy(request); t.is(request.method, 'GET'); await getStream(stream); }); test('has redirect event', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.get('/redirect', redirectHandler); const stream = got.stream('redirect'); const {headers} = await pEvent(stream, 'redirect'); t.is(headers.location, '/'); await getStream(stream); }); test('has response event', withServer, async (t, server, got) => { server.get('/', defaultHandler); const {statusCode} = await pEvent(got.stream(''), 'response'); t.is(statusCode, 200); }); test('has error event', withServer, async (t, server, got) => { server.get('/', errorHandler); const stream = got.stream(''); await t.throwsAsync(pEvent(stream, 'response'), { instanceOf: got.HTTPError, message: 'Response code 404 (Not Found)' }); }); test('has error event #2', async t => { const stream = got.stream('http://doesntexist'); try { await pEvent(stream, 'response'); } catch (error) { t.regex(error.code, /ENOTFOUND|EAI_AGAIN/); } }); test('has response event if `options.throwHttpErrors` is false', withServer, async (t, server, got) => { server.get('/', errorHandler); const {statusCode} = await pEvent(got.stream({throwHttpErrors: false}), 'response'); t.is(statusCode, 404); }); test('accepts `options.body` as a Stream', withServer, async (t, server, got) => { server.post('/', postHandler); const stream = got.stream.post({body: toReadableStream('wow')}); t.is(await getStream(stream), 'wow'); }); test('redirect response contains old url', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.get('/redirect', redirectHandler); const {requestUrl} = await pEvent(got.stream('redirect'), 'response'); t.is(requestUrl, `${server.url}/redirect`); }); 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(); }); test('piping works', withServer, async (t, server, got) => { server.get('/', defaultHandler); t.is(await getStream(got.stream('')), 'ok'); t.is(await getStream(got.stream('').on('foobar', () => {})), 'ok'); }); test('proxying headers works', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.get('/proxy', async (_request, response) => { await pStreamPipeline( got.stream(''), response ); }); const {headers, body} = await got('proxy'); t.is(headers.unicorn, 'rainbow'); t.is(headers['content-encoding'], undefined); t.is(body, 'ok'); }); test('piping server request to Got proxies also headers', withServer, async (t, server, got) => { server.get('/', headersHandler); server.get('/proxy', async (request, response) => { await pStreamPipeline( request, got.stream(''), response ); }); const {foo}: {foo: string} = await got('proxy', { headers: { foo: 'bar' } }).json(); t.is(foo, 'bar'); }); test('skips proxying headers after server has sent them already', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.get('/proxy', async (_request, response) => { response.writeHead(200); await pStreamPipeline( got.stream(''), response ); }); const {headers} = await got('proxy'); t.is(headers.unicorn, undefined); }); test('throws when trying to proxy through a closed stream', withServer, async (t, server, got) => { server.get('/', defaultHandler); const stream = got.stream(''); const promise = getStream(stream); stream.once('data', () => { t.throws(() => { stream.pipe(new PassThroughStream()); }, { message: 'Failed to pipe. The response has been emitted already.' }); }); await promise; }); test('proxies `content-encoding` header when `options.decompress` is false', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.get('/proxy', async (_request, response) => { await pStreamPipeline( got.stream({decompress: false}), response ); }); const {headers} = await got('proxy'); t.is(headers.unicorn, 'rainbow'); t.is(headers['content-encoding'], 'gzip'); }); { const nodejsMajorVersion = Number(process.versions.node.split('.')[0]); const testFn = nodejsMajorVersion < 14 ? test.failing : test; testFn('destroying got.stream() destroys the request - `request` event', withServer, async (t, server, got) => { server.get('/', defaultHandler); const stream = got.stream(''); const request = await pEvent(stream, 'request'); stream.destroy(); t.truthy(request.destroyed); }); testFn('destroying got.stream() destroys the request - `response` event', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.write('hello'); }); const stream = got.stream(''); const request = await pEvent(stream, 'request'); await pEvent(stream, 'response'); stream.destroy(); t.truthy(request.destroyed); }); } test('piping to got.stream.put()', withServer, async (t, server, got) => { server.get('/', defaultHandler); server.put('/post', postHandler); await t.notThrowsAsync(async () => { await getStream( stream.pipeline( got.stream(''), got.stream.put('post'), () => {} ) ); }); }); // See https://github.com/nodejs/node/issues/35237 // eslint-disable-next-line ava/no-skip-test test.skip('no unhandled body stream errors', async t => { const body = new FormData(); body.append('upload', fs.createReadStream('/bin/sh')); await t.throwsAsync(got.post(`https://offlinesite${Date.now()}.com`, { body }), { code: 'ENOTFOUND' }); }); test('works with pipeline', async t => { await t.throwsAsync(pStreamPipeline( new stream.Readable({ read() { this.push(null); } }), got.stream.put('http://localhost:7777') ), { instanceOf: RequestError, message: 'connect ECONNREFUSED 127.0.0.1:7777' }); }); test('errors have body', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.setHeader('set-cookie', 'foo=bar'); response.end('yay'); }); const error = await t.throwsAsync(getStream(got.stream('', { cookieJar: { setCookie: async (_, __) => { throw new Error('snap'); }, getCookieString: async _ => '' } }))); t.is(error.message, 'snap'); t.is(error.response?.body, 'yay'); }); test('pipe can send modified headers', withServer, async (t, server, got) => { server.get('/foobar', (_request, response) => { response.setHeader('foo', 'bar'); response.end(); }); server.get('/', (_request, response) => { got.stream('foobar').on('response', response => { response.headers.foo = 'boo'; }).pipe(response); }); const {headers} = await got(''); t.is(headers.foo, 'boo'); }); test('the socket is alive on a successful pipeline', withServer, async (t, server, got) => { const payload = 'ok'; server.get('/', (_request, response) => { response.end(payload); }); const gotStream = got.stream(''); t.is(gotStream.socket, undefined); const receiver = new stream.PassThrough(); await promisify(stream.pipeline)(gotStream, receiver); t.is(await getStream(receiver), payload); t.truthy(gotStream.socket); t.false(gotStream.socket!.destroyed); }); test('async iterator works', withServer, async (t, server, got) => { const payload = 'ok'; server.get('/', (_request, response) => { response.end(payload); }); const gotStream = got.stream(''); const chunks = []; for await (const chunk of gotStream) { chunks.push(chunk); } t.is(Buffer.concat(chunks).toString(), payload); }); if (process.versions.node.split('.')[0] <= '12') { test('does not emit end event on error', withServer, async (t, server, got) => { server.get('/', infiniteHandler); await t.notThrowsAsync(new Promise((resolve, reject) => { got.stream({ timeout: 100, hooks: { beforeError: [ async error => { await new Promise(resolve => { setTimeout(resolve, 50); }); return error; } ] } }).once('end', () => { reject(new Error('Stream has ended before erroring')); }).once('error', resolve).resume(); })); }); } // Test only on Linux const testFn = process.platform === 'linux' ? test : test.skip; testFn('it sends a body of file with size on stat = 0', withServer, async (t, server, got) => { server.post('/', async (request, response) => { response.end(await getStream(request)); }); const response = await got.post({ body: fs.createReadStream('/proc/cpuinfo') }); t.truthy(response.body); }); got-11.8.5/test/timeout.ts000066400000000000000000000364361424347352000154340ustar00rootroot00000000000000import {promisify} from 'util'; import {EventEmitter} from 'events'; import {PassThrough as PassThroughStream} from 'stream'; import stream = require('stream'); import http = require('http'); import net = require('net'); import getStream = require('get-stream'); import test from 'ava'; import delay = require('delay'); import CacheableLookup from 'cacheable-lookup'; import {Handler} from 'express'; import pEvent = require('p-event'); import got, {TimeoutError} from '../source'; import timedOut from '../source/core/utils/timed-out'; import slowDataStream from './helpers/slow-data-stream'; import {GlobalClock} from './helpers/types'; import withServer, {withServerAndFakeTimers, withHttpsServer} from './helpers/with-server'; const pStreamPipeline = promisify(stream.pipeline); const requestDelay = 800; const errorMatcher = { instanceOf: got.TimeoutError, code: 'ETIMEDOUT' }; const keepAliveAgent = new http.Agent({ keepAlive: true }); const defaultHandler = (clock: GlobalClock): Handler => (request, response) => { request.resume(); request.on('end', () => { clock.tick(requestDelay); response.end('OK'); }); }; const downloadHandler = (clock: GlobalClock): Handler => (_request, response) => { response.writeHead(200, { 'transfer-encoding': 'chunked' }); response.flushHeaders(); setImmediate(async () => { await pStreamPipeline(slowDataStream(clock), response); }); }; test.serial('timeout option', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.throwsAsync( got({ timeout: 1, retry: 0 }), { ...errorMatcher, message: 'Timeout awaiting \'request\' for 1ms' } ); }); test.serial('timeout option as object', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.throwsAsync( got({ timeout: {request: 1}, retry: 0 }), { ...errorMatcher, message: 'Timeout awaiting \'request\' for 1ms' } ); }); test.serial('socket timeout', async t => { await t.throwsAsync( got('https://example.com', { timeout: {socket: 1}, retry: 0, request: () => { const stream = new PassThroughStream(); // @ts-expect-error Mocking the behaviour of a ClientRequest stream.setTimeout = (ms, callback) => { process.nextTick(callback); }; // @ts-expect-error Mocking the behaviour of a ClientRequest stream.abort = () => {}; stream.resume(); return stream as unknown as http.ClientRequest; } }), { instanceOf: got.TimeoutError, code: 'ETIMEDOUT', message: 'Timeout awaiting \'socket\' for 1ms' } ); }); test.serial('send timeout', withServerAndFakeTimers, async (t, server, got, clock) => { server.post('/', defaultHandler(clock)); await t.throwsAsync( got.post({ timeout: {send: 1}, body: new stream.PassThrough(), retry: 0 }).on('request', request => { request.once('socket', socket => { socket.once('connect', () => { clock.tick(10); }); }); }), { ...errorMatcher, message: 'Timeout awaiting \'send\' for 1ms' } ); }); test.serial('send timeout (keepalive)', withServerAndFakeTimers, async (t, server, got, clock) => { server.post('/', defaultHandler(clock)); server.get('/prime', (_request, response) => { response.end('ok'); }); await got('prime', {agent: {http: keepAliveAgent}}); await t.throwsAsync( got.post({ agent: { http: keepAliveAgent }, timeout: {send: 1}, retry: 0, body: slowDataStream(clock) }).on('request', (request: http.ClientRequest) => { request.once('socket', socket => { t.false(socket.connecting); socket.once('connect', () => { t.fail('\'connect\' event fired, invalidating test'); }); }); }), { ...errorMatcher, message: 'Timeout awaiting \'send\' for 1ms' } ); }); test.serial('response timeout', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.throwsAsync( got({ timeout: {response: 1}, retry: 0 }), { ...errorMatcher, message: 'Timeout awaiting \'response\' for 1ms' } ); }); test.serial('response timeout unaffected by slow upload', withServerAndFakeTimers, async (t, server, got, clock) => { server.post('/', defaultHandler(clock)); await t.notThrowsAsync(got.post({ retry: 0, body: slowDataStream(clock) })); }); test.serial('response timeout unaffected by slow download', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', downloadHandler(clock)); await t.notThrowsAsync(got({ timeout: {response: 200}, retry: 0 })); clock.tick(100); }); test.serial('response timeout (keepalive)', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); server.get('/prime', (_request, response) => { response.end('ok'); }); await got('prime', {agent: {http: keepAliveAgent}}); const request = got({ agent: { http: keepAliveAgent }, timeout: {response: 1}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.once('socket', socket => { t.false(socket.connecting); socket.once('connect', () => { t.fail('\'connect\' event fired, invalidating test'); }); }); }); await t.throwsAsync(request, { ...errorMatcher, message: 'Timeout awaiting \'response\' for 1ms' }); }); test.serial('connect timeout', withServerAndFakeTimers, async (t, _server, got, clock) => { await t.throwsAsync( got({ createConnection: options => { const socket = new net.Socket(options as Record as net.SocketConstructorOpts); // @ts-expect-error We know that it is readonly, but we have to test it socket.connecting = true; setImmediate(() => { socket.emit('lookup', null, '127.0.0.1', 4, 'localhost'); }); return socket; }, timeout: {connect: 1}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.on('socket', () => { clock.runAll(); }); }), { ...errorMatcher, message: 'Timeout awaiting \'connect\' for 1ms' } ); }); test.serial('connect timeout (ip address)', withServerAndFakeTimers, async (t, _server, _got, clock) => { await t.throwsAsync( got({ url: 'http://127.0.0.1', createConnection: options => { const socket = new net.Socket(options as Record as net.SocketConstructorOpts); // @ts-expect-error We know that it is readonly, but we have to test it socket.connecting = true; return socket; }, timeout: {connect: 1}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.on('socket', () => { clock.runAll(); }); }), { ...errorMatcher, message: 'Timeout awaiting \'connect\' for 1ms' } ); }); test.serial('secureConnect timeout', withHttpsServer({}, true), async (t, _server, got, clock) => { await t.throwsAsync( got({ createConnection: options => { const socket = new net.Socket(options as Record as net.SocketConstructorOpts); // @ts-expect-error We know that it is readonly, but we have to test it socket.connecting = true; setImmediate(() => { socket.emit('lookup', null, '127.0.0.1', 4, 'localhost'); setImmediate(() => { socket.emit('connect'); }); }); return socket; }, timeout: {secureConnect: 0}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.on('socket', () => { clock!.runAll(); }); }), { ...errorMatcher, message: 'Timeout awaiting \'secureConnect\' for 0ms' } ); }); test('secureConnect timeout not breached', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await t.notThrowsAsync(got({ timeout: {secureConnect: 200}, retry: 0, https: { rejectUnauthorized: false } })); }); test.serial('lookup timeout', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.throwsAsync( got({ lookup: () => {}, timeout: {lookup: 1}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.on('socket', () => { clock.runAll(); }); }), { ...errorMatcher, message: 'Timeout awaiting \'lookup\' for 1ms' } ); }); test.serial('lookup timeout no error (ip address)', withServerAndFakeTimers, async (t, server, _got, clock) => { server.get('/', defaultHandler(clock)); await t.notThrowsAsync(got({ url: `http://127.0.0.1:${server.port}`, timeout: {lookup: 1}, retry: 0 })); }); test.serial('lookup timeout no error (keepalive)', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); server.get('/prime', (_request, response) => { response.end('ok'); }); await got('prime', {agent: {http: keepAliveAgent}}); await t.notThrowsAsync(got({ agent: {http: keepAliveAgent}, timeout: {lookup: 1}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.once('connect', () => { t.fail('connect event fired, invalidating test'); }); })); keepAliveAgent.destroy(); }); test.serial('retries on timeout', withServer, async (t, server, got) => { server.get('/', () => {}); let hasTried = false; await t.throwsAsync(got({ timeout: 1, retry: { calculateDelay: () => { if (hasTried) { return 0; } hasTried = true; return 1; } } }), { ...errorMatcher, message: 'Timeout awaiting \'request\' for 1ms' }); t.true(hasTried); }); test.serial('timeout with streams', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); const stream = got.stream({ timeout: 0, retry: 0 }); await t.throwsAsync(pEvent(stream, 'response'), {code: 'ETIMEDOUT'}); }); test.serial('no error emitted when timeout is not breached (stream)', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); const stream = got.stream({ retry: 0, timeout: { request: requestDelay * 2 } }); await t.notThrowsAsync(getStream(stream)); }); test.serial('no error emitted when timeout is not breached (promise)', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.notThrowsAsync(got({ retry: 0, timeout: { request: requestDelay * 2 } })); }); test.serial('no unhandled `socket hung up` errors', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); await t.throwsAsync( got({retry: 0, timeout: requestDelay / 2}), {instanceOf: got.TimeoutError} ); }); // TODO: use fakeTimers here test.serial('no unhandled timeout errors', withServer, async (t, _server, got) => { await t.throwsAsync(got({ retry: 0, timeout: 100, request: (...args: any[]) => { // @ts-expect-error const result = http.request(...args); result.once('socket', () => { result.socket?.destroy(); }); return result; } }), {message: 'socket hang up'}); await delay(200); }); // TODO: use fakeTimers here test.serial('no unhandled timeout errors #2', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.write('Hello world!'); }); const gotPromise = got('', { timeout: 20, retry: { calculateDelay: ({computedValue}) => { if (computedValue) { return 10; } return 0; }, limit: 1 } }); await t.throwsAsync(gotPromise, {instanceOf: TimeoutError}); await delay(100); }); test.serial('no more timeouts after an error', withServer, async (t, _server, got) => { const {setTimeout} = global; const {clearTimeout} = global; // @ts-expect-error global.setTimeout = (callback, _ms, ...args) => { const timeout = { isCleared: false }; process.nextTick(() => { if (timeout.isCleared) { return; } callback(...args); }); return timeout; }; // @ts-expect-error global.clearTimeout = timeout => { if (timeout) { timeout.isCleared = true; } }; await t.throwsAsync(got(`http://${Date.now()}.dev`, { retry: 1, timeout: { lookup: 1, connect: 1, secureConnect: 1, socket: 1, response: 1, send: 1, request: 1 } }), {instanceOf: got.TimeoutError}); await delay(100); global.setTimeout = setTimeout; global.clearTimeout = clearTimeout; }); test.serial('socket timeout is canceled on error', withServerAndFakeTimers, async (t, _server, got, clock) => { const message = 'oh, snap!'; const promise = got({ timeout: {socket: 50}, retry: 0 }).on('request', (request: http.ClientRequest) => { request.destroy(new Error(message)); }); await t.throwsAsync(promise, {message}); // Wait a bit more to check if there are any unhandled errors clock.tick(100); }); test.serial('no memory leak when using socket timeout and keepalive agent', withServerAndFakeTimers, async (t, server, got, clock) => { server.get('/', defaultHandler(clock)); let request: any; await got({ agent: {http: keepAliveAgent}, timeout: {socket: requestDelay * 2} }).on('request', _request => { request = _request; }); t.is(request.timeoutCb, null); keepAliveAgent.destroy(); }); test('ensure there are no new timeouts after cancelation', t => { const emitter = new EventEmitter(); const socket = new EventEmitter(); (socket as any).connecting = true; timedOut(emitter as http.ClientRequest, { connect: 1 }, { hostname: '127.0.0.1' })(); emitter.emit('socket', socket); socket.emit('lookup', null); t.is(socket.listenerCount('connect'), 0); }); test('double calling timedOut has no effect', t => { const emitter = new EventEmitter(); const attach = (): () => void => timedOut(emitter as http.ClientRequest, { connect: 1 }, { hostname: '127.0.0.1' }); attach(); attach(); t.is(emitter.listenerCount('socket'), 1); }); test.serial('doesn\'t throw on early lookup', withServerAndFakeTimers, async (t, server, got) => { server.get('/', (_request, response) => { response.end('ok'); }); await t.notThrowsAsync(got('', { timeout: { lookup: 1 }, retry: 0, // @ts-expect-error lookup: (...[_hostname, options, callback]: Parameters) => { if (typeof options === 'function') { callback = options; } // @ts-expect-error This should be fixed in upstream callback(null, '127.0.0.1', 4); } })); }); // TODO: use fakeTimers here test.serial('no unhandled `Premature close` error', withServer, async (t, server, got) => { server.get('/', async (_request, response) => { response.write('hello'); }); await t.throwsAsync(got({ timeout: 10, retry: 0 }), {message: 'Timeout awaiting \'request\' for 10ms'}); await delay(20); }); // TODO: use fakeTimers here test.serial('cancelling the request removes timeouts', withServer, async (t, server, got) => { server.get('/', (_request, response) => { response.write('hello'); }); const promise = got({ timeout: 500, retry: 0 }).on('downloadProgress', () => { promise.cancel(); }).on('request', request => { request.on('error', error => { if (error.message === 'Timeout awaiting \'request\' for 500ms') { t.fail(error.message); } }); }); await t.throwsAsync(promise, {message: 'Promise was canceled'}); await delay(1000); }); test('timeouts are emitted ASAP', async t => { const timeout = 500; const marginOfError = 100; const error = await t.throwsAsync(got('http://192.0.2.1/test', { retry: 0, timeout }), {instanceOf: TimeoutError}); t.true(error.timings.phases.total! < (timeout + marginOfError)); }); got-11.8.5/test/types/000077500000000000000000000000001424347352000145265ustar00rootroot00000000000000got-11.8.5/test/types/create-test-server/000077500000000000000000000000001424347352000202525ustar00rootroot00000000000000got-11.8.5/test/types/create-test-server/index.d.ts000066400000000000000000000006571424347352000221630ustar00rootroot00000000000000declare module 'create-test-server' { import {Express} from 'express'; function createTestServer(options: unknown): Promise; export = createTestServer; namespace createTestServer { export interface TestServer extends Express { caCert: string | Buffer | Array; port: number; url: string; sslPort: number; sslUrl: string; close: () => Promise; } } } got-11.8.5/test/types/slow-stream/000077500000000000000000000000001424347352000170035ustar00rootroot00000000000000got-11.8.5/test/types/slow-stream/index.d.ts000066400000000000000000000000361424347352000207030ustar00rootroot00000000000000declare module 'slow-stream'; got-11.8.5/test/unix-socket.ts000066400000000000000000000036011424347352000162030ustar00rootroot00000000000000import {format} from 'util'; import test from 'ava'; import {Handler} from 'express'; import got from '../source'; import {withSocketServer} from './helpers/with-server'; const okHandler: Handler = (_request, response) => { response.end('ok'); }; const redirectHandler: Handler = (_request, response) => { response.writeHead(302, { location: 'foo' }); response.end(); }; if (process.platform !== 'win32') { test('works', withSocketServer, async (t, server) => { server.on('/', okHandler); const url = format('http://unix:%s:%s', server.socketPath, '/'); t.is((await got(url)).body, 'ok'); }); test('protocol-less works', withSocketServer, async (t, server) => { server.on('/', okHandler); const url = format('unix:%s:%s', server.socketPath, '/'); t.is((await got(url)).body, 'ok'); }); test('address with : works', withSocketServer, async (t, server) => { server.on('/foo:bar', okHandler); const url = format('unix:%s:%s', server.socketPath, '/foo:bar'); t.is((await got(url)).body, 'ok'); }); test('throws on invalid URL', async t => { try { await got('unix:', {retry: 0}); } catch (error) { t.regex(error.code, /ENOTFOUND|EAI_AGAIN/); } }); test('works when extending instances', withSocketServer, async (t, server) => { server.on('/', okHandler); const url = format('unix:%s:%s', server.socketPath, '/'); const instance = got.extend({prefixUrl: url}); t.is((await instance('')).body, 'ok'); }); test('passes search params', withSocketServer, async (t, server) => { server.on('/?a=1', okHandler); const url = format('http://unix:%s:%s', server.socketPath, '/?a=1'); t.is((await got(url)).body, 'ok'); }); } test('redirects work', withSocketServer, async (t, server) => { server.on('/', redirectHandler); server.on('/foo', okHandler); const url = format('http://unix:%s:%s', server.socketPath, '/'); t.is((await got(url)).body, 'ok'); }); got-11.8.5/test/url-to-options.ts000066400000000000000000000063001424347352000166440ustar00rootroot00000000000000import url = require('url'); import {URL} from 'url'; import test from 'ava'; import urlToOptions from '../source/core/utils/url-to-options'; test('converts node legacy URL to options', t => { const exampleUrl = 'https://user:password@github.com:443/say?hello=world#bang'; const parsedUrl = url.parse(exampleUrl); const options = urlToOptions(parsedUrl); const expected = { hash: '#bang', host: 'github.com:443', hostname: 'github.com', href: exampleUrl, path: '/say?hello=world', pathname: '/say', port: 443, protocol: 'https:', search: '?hello=world' }; t.deepEqual(options, expected); }); test('converts URL to options', t => { const exampleUrl = 'https://user:password@github.com:443/say?hello=world#bang'; const parsedUrl = new URL(exampleUrl); const options = urlToOptions(parsedUrl); const expected = { auth: 'user:password', hash: '#bang', host: 'github.com', hostname: 'github.com', href: 'https://user:password@github.com/say?hello=world#bang', path: '/say?hello=world', pathname: '/say', protocol: 'https:', search: '?hello=world' }; t.deepEqual(options, expected); }); test('converts IPv6 URL to options', t => { const IPv6URL = 'https://[2001:cdba::3257:9652]:443/'; const parsedUrl = new URL(IPv6URL); const options = urlToOptions(parsedUrl); const expected = { hash: '', host: '[2001:cdba::3257:9652]', hostname: '2001:cdba::3257:9652', href: 'https://[2001:cdba::3257:9652]/', path: '/', pathname: '/', protocol: 'https:', search: '' }; t.deepEqual(options, expected); }); test('only adds port to options for URLs with ports', t => { const noPortURL = 'https://github.com/'; const parsedUrl = new URL(noPortURL); const options = urlToOptions(parsedUrl); const expected = { hash: '', host: 'github.com', hostname: 'github.com', href: 'https://github.com/', path: '/', pathname: '/', protocol: 'https:', search: '' }; t.deepEqual(options, expected); t.false(Reflect.has(options, 'port')); }); test('does not concat null search to path', t => { const exampleUrl = 'https://github.com/'; const parsedUrl = url.parse(exampleUrl); t.is(parsedUrl.search, null); const options = urlToOptions(parsedUrl); const expected = { hash: null, host: 'github.com', hostname: 'github.com', href: 'https://github.com/', path: '/', pathname: '/', protocol: 'https:', search: null }; t.deepEqual(options, expected); }); test('does not add null port to options', t => { const exampleUrl = 'https://github.com/'; const parsedUrl = url.parse(exampleUrl); t.is(parsedUrl.port, null); const options = urlToOptions(parsedUrl); const expected = { hash: null, host: 'github.com', hostname: 'github.com', href: 'https://github.com/', path: '/', pathname: '/', protocol: 'https:', search: null }; t.deepEqual(options, expected); }); test('does not throw if there is no hostname', t => { t.notThrows(() => urlToOptions({} as URL)); }); test('null password', t => { const options = urlToOptions({ username: 'foo', password: null } as any); t.is(options.auth, 'foo:'); }); test('null username', t => { const options = urlToOptions({ username: null, password: 'bar' } as any); t.is(options.auth, ':bar'); }); got-11.8.5/test/weakable-map.ts000066400000000000000000000011021424347352000162520ustar00rootroot00000000000000import test from 'ava'; import WeakableMap from '../source/core/utils/weakable-map'; test('works as expected', t => { const weakable = new WeakableMap(); weakable.set('hello', 'world'); t.true(weakable.has('hello')); t.false(weakable.has('foobar')); t.is(weakable.get('hello'), 'world'); t.is(weakable.get('foobar'), undefined); const object = {}; const anotherObject = {}; weakable.set(object, 'world'); t.true(weakable.has(object)); t.false(weakable.has(anotherObject)); t.is(weakable.get(object), 'world'); t.is(weakable.get(anotherObject), undefined); }); got-11.8.5/tsconfig.json000066400000000000000000000003401424347352000151070ustar00rootroot00000000000000{ "extends": "@sindresorhus/tsconfig", "compilerOptions": { "outDir": "dist", "target": "es2018", // Node.js 10 "lib": [ "es2018", "es2019.string" ] }, "include": [ "source", "test", "benchmark" ] }