summaryrefslogtreecommitdiff
path: root/includes/external/addressbook/node_modules/http2-wrapper
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/addressbook/node_modules/http2-wrapper')
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/LICENSE21
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/README.md459
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/index.d.ts141
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/package.json81
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/agent.js796
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/auto.js206
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/client-request.js563
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/incoming-message.js73
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/index.js50
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/get-auth-headers.js17
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h1-over-h2.js90
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h1.js48
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h2.js32
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-hx.js40
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/initialize.js21
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js11
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/calculate-server-name.js29
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/check-type.js20
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/delay-async-destroy.js33
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/errors.js51
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js13
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/js-stream-socket.js8
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-events.js7
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js102
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-name.js11
-rw-r--r--includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-value.js17
26 files changed, 2940 insertions, 0 deletions
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/LICENSE b/includes/external/addressbook/node_modules/http2-wrapper/LICENSE
new file mode 100644
index 0000000..15ad2e8
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Szymon Marczak
+
+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.
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/README.md b/includes/external/addressbook/node_modules/http2-wrapper/README.md
new file mode 100644
index 0000000..6510a7d
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/README.md
@@ -0,0 +1,459 @@
+# http2-wrapper
+> HTTP/2 client, just with the familiar `https` API
+
+[![Node CI](https://github.com/szmarczak/http2-wrapper/workflows/Node%20CI/badge.svg)](https://github.com/szmarczak/http2-wrapper/actions)
+[![codecov](https://codecov.io/gh/szmarczak/http2-wrapper/branch/master/graph/badge.svg)](https://codecov.io/gh/szmarczak/http2-wrapper)
+[![npm](https://img.shields.io/npm/dm/http2-wrapper.svg)](https://www.npmjs.com/package/http2-wrapper)
+[![install size](https://packagephobia.now.sh/badge?p=http2-wrapper)](https://packagephobia.now.sh/result?p=http2-wrapper)
+
+This package was created to support HTTP/2 without the need to rewrite your code.<br>
+I recommend adapting to the [`http2`](https://nodejs.org/api/http2.html) module if possible - it's much simpler to use and has many cool features!
+
+**Tip**: `http2-wrapper` is very useful when you rely on other modules that use the HTTP/1 API and you want to support HTTP/2.
+
+**Pro Tip**: While the native `http2` doesn't have agents yet, you can use `http2-wrapper` Agents and still operate on the native HTTP/2 streams.
+
+## Installation
+
+> `$ npm install http2-wrapper`<br>
+> `$ yarn add http2-wrapper`
+
+## Usage
+
+```js
+const http2 = require('http2-wrapper');
+
+const options = {
+ hostname: 'nghttp2.org',
+ protocol: 'https:',
+ path: '/httpbin/post',
+ method: 'POST',
+ headers: {
+ 'content-length': 6
+ }
+};
+
+const request = http2.request(options, response => {
+ console.log('statusCode:', response.statusCode);
+ console.log('headers:', response.headers);
+
+ const body = [];
+ response.on('data', chunk => {
+ body.push(chunk);
+ });
+ response.on('end', () => {
+ console.log('body:', Buffer.concat(body).toString());
+ });
+});
+
+request.on('error', console.error);
+
+request.write('123');
+request.end('456');
+
+// statusCode: 200
+// headers: [Object: null prototype] {
+// ':status': 200,
+// date: 'Fri, 27 Sep 2019 19:45:46 GMT',
+// 'content-type': 'application/json',
+// 'access-control-allow-origin': '*',
+// 'access-control-allow-credentials': 'true',
+// 'content-length': '239',
+// 'x-backend-header-rtt': '0.002516',
+// 'strict-transport-security': 'max-age=31536000',
+// server: 'nghttpx',
+// via: '1.1 nghttpx',
+// 'alt-svc': 'h3-23=":4433"; ma=3600',
+// 'x-frame-options': 'SAMEORIGIN',
+// 'x-xss-protection': '1; mode=block',
+// 'x-content-type-options': 'nosniff'
+// }
+// body: {
+// "args": {},
+// "data": "123456",
+// "files": {},
+// "form": {},
+// "headers": {
+// "Content-Length": "6",
+// "Host": "nghttp2.org"
+// },
+// "json": 123456,
+// "origin": "xxx.xxx.xxx.xxx",
+// "url": "https://nghttp2.org/httpbin/post"
+// }
+```
+
+## API
+
+**Note:** The `session` option was renamed to `tlsSession` for better readability.
+
+**Note:** The `timeout` option applies to HTTP/2 streams only. In order to set session timeout, pass an Agent with custom `timeout` option set.
+
+### http2.auto(url, options, callback)
+
+Performs [ALPN](https://nodejs.org/api/tls.html#tls_alpn_and_sni) negotiation.
+Returns a Promise giving proper `ClientRequest` instance (depending on the ALPN).
+
+**Note**: The `agent` option represents an object with `http`, `https` and `http2` properties.
+
+```js
+const http2 = require('http2-wrapper');
+
+const options = {
+ hostname: 'httpbin.org',
+ protocol: 'http:', // Try changing this to https:
+ path: '/post',
+ method: 'POST',
+ headers: {
+ 'content-length': 6
+ }
+};
+
+(async () => {
+ try {
+ const request = await http2.auto(options, response => {
+ console.log('statusCode:', response.statusCode);
+ console.log('headers:', response.headers);
+
+ const body = [];
+ response.on('data', chunk => body.push(chunk));
+ response.on('end', () => {
+ console.log('body:', Buffer.concat(body).toString());
+ });
+ });
+
+ request.on('error', console.error);
+
+ request.write('123');
+ request.end('456');
+ } catch (error) {
+ console.error(error);
+ }
+})();
+
+// statusCode: 200
+// headers: { connection: 'close',
+// server: 'gunicorn/19.9.0',
+// date: 'Sat, 15 Dec 2018 18:19:32 GMT',
+// 'content-type': 'application/json',
+// 'content-length': '259',
+// 'access-control-allow-origin': '*',
+// 'access-control-allow-credentials': 'true',
+// via: '1.1 vegur' }
+// body: {
+// "args": {},
+// "data": "123456",
+// "files": {},
+// "form": {},
+// "headers": {
+// "Connection": "close",
+// "Content-Length": "6",
+// "Host": "httpbin.org"
+// },
+// "json": 123456,
+// "origin": "xxx.xxx.xxx.xxx",
+// "url": "http://httpbin.org/post"
+// }
+```
+
+### http2.auto.protocolCache
+
+An instance of [`quick-lru`](https://github.com/sindresorhus/quick-lru) used for ALPN cache.
+
+There is a maximum of 100 entries. You can modify the limit through `protocolCache.maxSize` - note that the change will be visible globally.
+
+### http2.auto.createResolveProtocol(cache, queue, connect)
+
+#### cache
+
+Type: `Map<string, string>`
+
+This is the store where cached ALPN protocols are put into.
+
+#### queue
+
+Type: `Map<string, Promise>`
+
+This is the store that contains pending ALPN negotiation promises.
+
+#### connect
+
+Type: `(options, callback) => TLSSocket | Promise<TLSSocket>`
+
+See https://github.com/szmarczak/resolve-alpn#connect
+
+### http2.auto.resolveProtocol(options)
+
+Returns a `Promise<{alpnProtocol: string}>`.
+
+### http2.request(url, options, callback)
+
+Same as [`https.request`](https://nodejs.org/api/https.html#https_https_request_options_callback).
+
+##### options.h2session
+
+Type: `Http2Session`<br>
+
+The session used to make the actual request. If none provided, it will use `options.agent` to get one.
+
+### http2.get(url, options, callback)
+
+Same as [`https.get`](https://nodejs.org/api/https.html#https_https_get_options_callback).
+
+### new http2.ClientRequest(url, options, callback)
+
+Same as [`https.ClientRequest`](https://nodejs.org/api/https.html#https_class_https_clientrequest).
+
+### new http2.IncomingMessage(socket)
+
+Same as [`https.IncomingMessage`](https://nodejs.org/api/https.html#https_class_https_incomingmessage).
+
+### new http2.Agent(options)
+
+**Note:** this is **not** compatible with the classic `http.Agent`.
+
+Usage example:
+
+```js
+const http2 = require('http2-wrapper');
+
+class MyAgent extends http2.Agent {
+ createConnection(origin, options) {
+ console.log(`Connecting to ${http2.Agent.normalizeOrigin(origin)}`);
+ return http2.Agent.connect(origin, options);
+ }
+}
+
+http2.get({
+ hostname: 'google.com',
+ agent: new MyAgent()
+}, response => {
+ response.on('data', chunk => console.log(`Received chunk of ${chunk.length} bytes`));
+});
+```
+
+#### options
+
+Each option is an `Agent` property and can be changed later.
+
+##### timeout
+
+Type: `number`<br>
+Default: `0`
+
+If there's no activity after `timeout` milliseconds, the session will be closed. If `0`, no timeout is applied.
+
+##### maxSessions
+
+Type: `number`<br>
+Default: `Infinity`
+
+The maximum amount of sessions in total.
+
+##### maxEmptySessions
+
+Type: `number`<br>
+Default: `10`
+
+The maximum amount of empty sessions in total. An empty session is a session with no pending requests.
+
+##### maxCachedTlsSessions
+
+Type: `number`<br>
+Default: `100`
+
+The maximum amount of cached TLS sessions.
+
+#### agent.protocol
+
+Type: `string`<br>
+Default: `https:`
+
+#### agent.settings
+
+Type: `object`<br>
+Default: `{enablePush: false}`
+
+[Settings](https://nodejs.org/api/http2.html#http2_settings_object) used by the current agent instance.
+
+#### agent.normalizeOptions([options](https://github.com/szmarczak/http2-wrapper/blob/master/source/agent.js))
+
+Returns a string representing normalized options.
+
+```js
+Agent.normalizeOptions({servername: 'example.com'});
+// => ':::::::::::::::::::::::::::::::::::::'
+```
+
+#### agent.getSession(origin, options)
+
+##### [origin](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
+
+Type: `string` `URL` `object`
+
+Origin used to create new session.
+
+##### [options](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
+
+Type: `object`
+
+Options used to create new session.
+
+Returns a Promise giving free `Http2Session`. If no free sessions are found, a new one is created.
+
+A session is considered free when pending streams count is less than max concurrent streams settings.
+
+#### agent.getSession([origin](#origin), [options](options-1), listener)
+
+##### listener
+
+Type: `object`
+
+```
+{
+ reject: error => void,
+ resolve: session => void
+}
+```
+
+If the `listener` argument is present, the Promise will resolve immediately. It will use the `resolve` function to pass the session.
+
+#### agent.request([origin](#origin), [options](#options-1), [headers](https://nodejs.org/api/http2.html#http2_headers_object), [streamOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options))
+
+Returns a Promise giving `Http2Stream`.
+
+#### agent.createConnection([origin](#origin), [options](#options-1))
+
+Returns a new `TLSSocket`. It defaults to `Agent.connect(origin, options)`.
+
+#### agent.closeEmptySessions(count)
+
+##### count
+
+Type: `number`
+Default: `Number.POSITIVE_INFINITY`
+
+Makes an attempt to close empty sessions. Only sessions with 0 concurrent streams will be closed.
+
+#### agent.destroy(reason)
+
+Destroys **all** sessions.
+
+#### agent.emptySessionCount
+
+Type: `number`
+
+A number of empty sessions.
+
+#### agent.pendingSessionCount
+
+Type: `number`
+
+A number of pending sessions.
+
+#### agent.sessionCount
+
+Type: `number`
+
+A number of all sessions held by the Agent.
+
+#### Event: 'session'
+
+```js
+agent.on('session', session => {
+ // A new session has been created by the Agent.
+});
+```
+
+## Proxy support
+
+Currently `http2-wrapper` provides support for these proxies:
+
+- `HttpOverHttp2`
+- `HttpsOverHttp2`
+- `Http2OverHttp2`
+- `Http2OverHttp`
+- `Http2OverHttps`
+
+Any of the above can be accessed via `http2wrapper.proxies`. Check out the [`examples/proxies`](examples/proxies) directory to learn more.
+
+**Note:** If you use the `http2.auto` function, the real IP address will leak. `http2wrapper` is not aware of the context. It will create a connection to the end server using your real IP address to get the ALPN protocol. Then it will create another connection using proxy. To migitate this, you need to pass a custom `resolveProtocol` function as an option:
+
+```js
+const resolveAlpnProxy = new URL('https://username:password@localhost:8000');
+const connect = async (options, callback) => new Promise((resolve, reject) => {
+ const host = `${options.host}:${options.port}`;
+
+ (async () => {
+ try {
+ const request = await http2.auto(resolveAlpnProxy, {
+ method: 'CONNECT',
+ headers: {
+ host
+ },
+ path: host,
+
+ // For demo purposes only!
+ rejectUnauthorized: false,
+ });
+
+ request.end();
+
+ request.once('error', reject);
+
+ request.once('connect', (response, socket, head) => {
+ if (head.length > 0) {
+ reject(new Error(`Unexpected data before CONNECT tunnel: ${head.length} bytes`));
+
+ socket.destroy();
+ return;
+ }
+
+ const tlsSocket = tls.connect({
+ ...options,
+ socket
+ }, callback);
+
+ resolve(tlsSocket);
+ });
+ } catch (error) {
+ reject(error);
+ }
+ })();
+});
+
+// This is required to prevent leaking real IP address on ALPN negotiation
+const resolveProtocol = http2.auto.createResolveProtocol(new Map(), new Map(), connect);
+
+const request = await http2.auto('https://httpbin.org/anything', {
+ agent: {…},
+ resolveProtocol
+}, response => {
+ // Read the response here
+});
+
+request.end();
+```
+
+See [`unknown-over-unknown.js`](examples/proxies/unknown-over-unknown.js) to learn more.
+
+## Mirroring another server
+
+See [`examples/proxies/mirror.js`](examples/proxies/mirror.js) for an example.
+
+## [WebSockets over HTTP/2](https://tools.ietf.org/html/rfc8441)
+
+See [`examples/ws`](examples/ws) for an example.
+
+## Push streams
+
+See [`examples/push-stream`](examples/push-stream) for an example.
+
+## Related
+
+- [`got`](https://github.com/sindresorhus/got) - Simplified HTTP requests
+- [`http2-proxy`](https://github.com/nxtedition/node-http2-proxy) - A simple http/2 & http/1.1 spec compliant proxy helper for Node.
+
+## License
+
+MIT
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/index.d.ts b/includes/external/addressbook/node_modules/http2-wrapper/index.d.ts
new file mode 100644
index 0000000..474e019
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/index.d.ts
@@ -0,0 +1,141 @@
+// See https://github.com/facebook/jest/issues/2549
+// eslint-disable-next-line node/prefer-global/url
+import {URL} from 'url';
+import {EventEmitter} from 'events';
+import tls = require('tls');
+import http = require('http');
+import https = require('https');
+import http2 = require('http2');
+
+// Note: do not convert this to import from.
+import QuickLRU = require('quick-lru');
+
+export interface RequestOptions extends Omit<https.RequestOptions, 'session' | 'agent'> {
+ tlsSession?: tls.ConnectionOptions['session'];
+ h2session?: http2.ClientHttp2Session;
+ agent?: Agent | false;
+
+ // Required because @types/node is missing types
+ ALPNProtocols?: string[];
+}
+
+export interface AutoRequestOptions extends Omit<RequestOptions, 'agent' | 'h2session'> {
+ agent?: {
+ http?: http.Agent | false;
+ https?: https.Agent | false;
+ http2?: Agent | false;
+ };
+ resolveProtocol?: ResolveProtocolFunction;
+}
+
+export interface EntryFunction {
+ (): Promise<void>;
+
+ completed: boolean;
+ destroyed: boolean;
+ listeners: PromiseListeners;
+}
+
+export interface AgentOptions {
+ timeout?: number;
+ maxSessions?: number;
+ maxEmptySessions?: number;
+ maxCachedTlsSessions?: number;
+}
+
+export interface PromiseListeners {
+ resolve: (value: unknown) => void;
+ reject: (error: Error) => void;
+}
+
+export class Agent extends EventEmitter {
+ sessions: Record<string, http2.ClientHttp2Session[]>;
+ queue: Record<string, Record<string, EntryFunction>>;
+
+ timeout: number;
+ maxSessions: number;
+ maxEmptySessions: number;
+ protocol: string;
+ settings: http2.Settings;
+
+ tlsSessionCache: QuickLRU<string, string>;
+
+ emptySessionCount: number;
+ pendingSessionCount: number;
+ sessionCount: number;
+
+ constructor(options?: AgentOptions);
+
+ static connect(origin: URL, options: http2.SecureClientSessionOptions): tls.TLSSocket;
+
+ normalizeOptions(options: http2.ClientSessionRequestOptions): string;
+
+ getSession(origin: string | URL, options?: http2.SecureClientSessionOptions, listeners?: PromiseListeners): Promise<http2.ClientHttp2Session>;
+ request(origin: string | URL, options?: http2.SecureClientSessionOptions, headers?: http2.OutgoingHttpHeaders, streamOptions?: http2.ClientSessionRequestOptions): Promise<http2.ClientHttp2Stream>;
+
+ createConnection(origin: URL, options: http2.SecureClientSessionOptions): Promise<tls.TLSSocket>;
+
+ closeEmptySessions(count?: number): void;
+ destroy(reason?: Error): void;
+}
+
+export interface ProxyOptions {
+ headers?: http2.OutgoingHttpHeaders;
+ raw?: boolean;
+ url: URL | string;
+}
+
+export namespace proxies {
+ class HttpOverHttp2 extends http.Agent {
+ constructor(options: http.AgentOptions & {proxyOptions: ProxyOptions});
+ }
+
+ class HttpsOverHttp2 extends https.Agent {
+ constructor(options: https.AgentOptions & {proxyOptions: ProxyOptions});
+ }
+
+ class Http2OverHttp2 extends Agent {
+ constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
+ }
+
+ class Http2OverHttp extends Agent {
+ constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
+ }
+
+ class Http2OverHttps extends Agent {
+ constructor(options: AgentOptions & {proxyOptions: ProxyOptions});
+ }
+}
+
+export type RequestFunction<T, O = RequestOptions> =
+ ((url: string | URL, options?: O, callback?: (response: http.IncomingMessage) => void) => T) &
+ ((url: string | URL, callback?: (response: http.IncomingMessage) => void) => T) &
+ ((options: O, callback?: (response: http.IncomingMessage) => void) => T);
+
+export const globalAgent: Agent;
+
+export type ResolveProtocolResult = {
+ alpnProtocol: string;
+ socket?: tls.TLSSocket;
+ timeout?: boolean;
+};
+export type ResolveProtocolFunction = (options: AutoRequestOptions) => Promise<ResolveProtocolResult>;
+
+type Promisable<T> = T | Promise<T>;
+
+export type ResolveProtocolConnectFunction = (options: tls.ConnectionOptions, callback: () => void) => Promisable<tls.TLSSocket>;
+
+export const request: RequestFunction<http.ClientRequest>;
+export const get: RequestFunction<http.ClientRequest>;
+export const auto: RequestFunction<Promise<http.ClientRequest>, AutoRequestOptions> & {
+ protocolCache: QuickLRU<string, string>;
+ resolveProtocol: ResolveProtocolFunction;
+ createResolveProtocol: (cache: Map<string, string>, queue: Map<string, Promise<ResolveProtocolResult>>, connect?: ResolveProtocolConnectFunction) => ResolveProtocolFunction;
+};
+
+export {
+ ClientRequest,
+ IncomingMessage
+} from 'http';
+
+export * from 'http2';
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/package.json b/includes/external/addressbook/node_modules/http2-wrapper/package.json
new file mode 100644
index 0000000..e97ca22
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/package.json
@@ -0,0 +1,81 @@
+{
+ "name": "http2-wrapper",
+ "version": "2.2.0",
+ "description": "HTTP2 client, just with the familiar `https` API",
+ "main": "source",
+ "types": "index.d.ts",
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "scripts": {
+ "test": "xo && nyc --reporter=lcovonly --reporter=text --reporter=html ava && tsd"
+ },
+ "files": [
+ "source",
+ "index.d.ts"
+ ],
+ "keywords": [
+ "http2",
+ "https",
+ "http",
+ "request"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/szmarczak/http2-wrapper.git"
+ },
+ "author": "Szymon Marczak",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/szmarczak/http2-wrapper/issues"
+ },
+ "homepage": "https://github.com/szmarczak/http2-wrapper#readme",
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.2.0"
+ },
+ "devDependencies": {
+ "@sindresorhus/is": "^4.0.1",
+ "ava": "^3.15.0",
+ "benchmark": "^2.1.4",
+ "get-stream": "^6.0.1",
+ "got": "^11.8.2",
+ "http2-proxy": "^5.0.53",
+ "https-proxy-agent": "^5.0.0",
+ "lolex": "^6.0.0",
+ "many-keys-map": "^1.0.3",
+ "nyc": "^15.1.0",
+ "p-event": "^4.2.0",
+ "tempy": "^1.0.1",
+ "to-readable-stream": "^2.1.0",
+ "tsd": "^0.17.0",
+ "websocket-stream": "^5.5.2",
+ "ws": "^7.5.3",
+ "xo": "0.39.1"
+ },
+ "ava": {
+ "timeout": "10s"
+ },
+ "nyc": {
+ "include": [
+ "source"
+ ]
+ },
+ "xo": {
+ "rules": {
+ "unicorn/no-for-loop": "off",
+ "unicorn/prefer-module": "off",
+ "comma-dangle": "off",
+ "@typescript-eslint/comma-dangle": "off",
+ "quotes": [
+ "error",
+ "single",
+ {
+ "avoidEscape": true,
+ "allowTemplateLiterals": true
+ }
+ ],
+ "operator-linebreak": ["error", "before"]
+ }
+ }
+}
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/agent.js b/includes/external/addressbook/node_modules/http2-wrapper/source/agent.js
new file mode 100644
index 0000000..922d202
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/agent.js
@@ -0,0 +1,796 @@
+'use strict';
+// See https://github.com/facebook/jest/issues/2549
+// eslint-disable-next-line node/prefer-global/url
+const {URL} = require('url');
+const EventEmitter = require('events');
+const tls = require('tls');
+const http2 = require('http2');
+const QuickLRU = require('quick-lru');
+const delayAsyncDestroy = require('./utils/delay-async-destroy.js');
+
+const kCurrentStreamCount = Symbol('currentStreamCount');
+const kRequest = Symbol('request');
+const kOriginSet = Symbol('cachedOriginSet');
+const kGracefullyClosing = Symbol('gracefullyClosing');
+const kLength = Symbol('length');
+
+const nameKeys = [
+ // Not an Agent option actually
+ 'createConnection',
+
+ // `http2.connect()` options
+ 'maxDeflateDynamicTableSize',
+ 'maxSettings',
+ 'maxSessionMemory',
+ 'maxHeaderListPairs',
+ 'maxOutstandingPings',
+ 'maxReservedRemoteStreams',
+ 'maxSendHeaderBlockLength',
+ 'paddingStrategy',
+ 'peerMaxConcurrentStreams',
+ 'settings',
+
+ // `tls.connect()` source options
+ 'family',
+ 'localAddress',
+ 'rejectUnauthorized',
+
+ // `tls.connect()` secure context options
+ 'pskCallback',
+ 'minDHSize',
+
+ // `tls.connect()` destination options
+ // - `servername` is automatically validated, skip it
+ // - `host` and `port` just describe the destination server,
+ 'path',
+ 'socket',
+
+ // `tls.createSecureContext()` options
+ 'ca',
+ 'cert',
+ 'sigalgs',
+ 'ciphers',
+ 'clientCertEngine',
+ 'crl',
+ 'dhparam',
+ 'ecdhCurve',
+ 'honorCipherOrder',
+ 'key',
+ 'privateKeyEngine',
+ 'privateKeyIdentifier',
+ 'maxVersion',
+ 'minVersion',
+ 'pfx',
+ 'secureOptions',
+ 'secureProtocol',
+ 'sessionIdContext',
+ 'ticketKeys'
+];
+
+const getSortedIndex = (array, value, compare) => {
+ let low = 0;
+ let high = array.length;
+
+ while (low < high) {
+ const mid = (low + high) >>> 1;
+
+ if (compare(array[mid], value)) {
+ low = mid + 1;
+ } else {
+ high = mid;
+ }
+ }
+
+ return low;
+};
+
+const compareSessions = (a, b) => a.remoteSettings.maxConcurrentStreams > b.remoteSettings.maxConcurrentStreams;
+
+// See https://tools.ietf.org/html/rfc8336
+const closeCoveredSessions = (where, session) => {
+ // Clients SHOULD NOT emit new requests on any connection whose Origin
+ // Set is a proper subset of another connection's Origin Set, and they
+ // SHOULD close it once all outstanding requests are satisfied.
+ for (let index = 0; index < where.length; index++) {
+ const coveredSession = where[index];
+
+ if (
+ // Unfortunately `.every()` returns true for an empty array
+ coveredSession[kOriginSet].length > 0
+
+ // The set is a proper subset when its length is less than the other set.
+ && coveredSession[kOriginSet].length < session[kOriginSet].length
+
+ // And the other set includes all elements of the subset.
+ && coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin))
+
+ // Makes sure that the session can handle all requests from the covered session.
+ && (coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount]) <= session.remoteSettings.maxConcurrentStreams
+ ) {
+ // This allows pending requests to finish and prevents making new requests.
+ gracefullyClose(coveredSession);
+ }
+ }
+};
+
+// This is basically inverted `closeCoveredSessions(...)`.
+const closeSessionIfCovered = (where, coveredSession) => {
+ for (let index = 0; index < where.length; index++) {
+ const session = where[index];
+
+ if (
+ coveredSession[kOriginSet].length > 0
+ && coveredSession[kOriginSet].length < session[kOriginSet].length
+ && coveredSession[kOriginSet].every(origin => session[kOriginSet].includes(origin))
+ && (coveredSession[kCurrentStreamCount] + session[kCurrentStreamCount]) <= session.remoteSettings.maxConcurrentStreams
+ ) {
+ gracefullyClose(coveredSession);
+
+ return true;
+ }
+ }
+
+ return false;
+};
+
+const gracefullyClose = session => {
+ session[kGracefullyClosing] = true;
+
+ if (session[kCurrentStreamCount] === 0) {
+ session.close();
+ }
+};
+
+class Agent extends EventEmitter {
+ constructor({timeout = 0, maxSessions = Number.POSITIVE_INFINITY, maxEmptySessions = 10, maxCachedTlsSessions = 100} = {}) {
+ super();
+
+ // SESSIONS[NORMALIZED_OPTIONS] = [];
+ this.sessions = {};
+
+ // The queue for creating new sessions. It looks like this:
+ // QUEUE[NORMALIZED_OPTIONS][NORMALIZED_ORIGIN] = ENTRY_FUNCTION
+ //
+ // It's faster when there are many origins. If there's only one, then QUEUE[`${options}:${origin}`] is faster.
+ // I guess object creation / deletion is causing the slowdown.
+ //
+ // The entry function has `listeners`, `completed` and `destroyed` properties.
+ // `listeners` is an array of objects containing `resolve` and `reject` functions.
+ // `completed` is a boolean. It's set to true after ENTRY_FUNCTION is executed.
+ // `destroyed` is a boolean. If it's set to true, the session will be destroyed if hasn't connected yet.
+ this.queue = {};
+
+ // Each session will use this timeout value.
+ this.timeout = timeout;
+
+ // Max sessions in total
+ this.maxSessions = maxSessions;
+
+ // Max empty sessions in total
+ this.maxEmptySessions = maxEmptySessions;
+
+ this._emptySessionCount = 0;
+ this._sessionCount = 0;
+
+ // We don't support push streams by default.
+ this.settings = {
+ enablePush: false,
+ initialWindowSize: 1024 * 1024 * 32 // 32MB, see https://github.com/nodejs/node/issues/38426
+ };
+
+ // Reusing TLS sessions increases performance.
+ this.tlsSessionCache = new QuickLRU({maxSize: maxCachedTlsSessions});
+ }
+
+ get protocol() {
+ return 'https:';
+ }
+
+ normalizeOptions(options) {
+ let normalized = '';
+
+ for (let index = 0; index < nameKeys.length; index++) {
+ const key = nameKeys[index];
+
+ normalized += ':';
+
+ if (options && options[key] !== undefined) {
+ normalized += options[key];
+ }
+ }
+
+ return normalized;
+ }
+
+ _processQueue() {
+ if (this._sessionCount >= this.maxSessions) {
+ this.closeEmptySessions(this.maxSessions - this._sessionCount + 1);
+ return;
+ }
+
+ // eslint-disable-next-line guard-for-in
+ for (const normalizedOptions in this.queue) {
+ // eslint-disable-next-line guard-for-in
+ for (const normalizedOrigin in this.queue[normalizedOptions]) {
+ const item = this.queue[normalizedOptions][normalizedOrigin];
+
+ // The entry function can be run only once.
+ if (!item.completed) {
+ item.completed = true;
+
+ item();
+ }
+ }
+ }
+ }
+
+ _isBetterSession(thisStreamCount, thatStreamCount) {
+ return thisStreamCount > thatStreamCount;
+ }
+
+ _accept(session, listeners, normalizedOrigin, options) {
+ let index = 0;
+
+ while (index < listeners.length && session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams) {
+ // We assume `resolve(...)` calls `request(...)` *directly*,
+ // otherwise the session will get overloaded.
+ listeners[index].resolve(session);
+
+ index++;
+ }
+
+ listeners.splice(0, index);
+
+ if (listeners.length > 0) {
+ this.getSession(normalizedOrigin, options, listeners);
+ listeners.length = 0;
+ }
+ }
+
+ getSession(origin, options, listeners) {
+ return new Promise((resolve, reject) => {
+ if (Array.isArray(listeners) && listeners.length > 0) {
+ listeners = [...listeners];
+
+ // Resolve the current promise ASAP, we're just moving the listeners.
+ // They will be executed at a different time.
+ resolve();
+ } else {
+ listeners = [{resolve, reject}];
+ }
+
+ try {
+ // Parse origin
+ if (typeof origin === 'string') {
+ origin = new URL(origin);
+ } else if (!(origin instanceof URL)) {
+ throw new TypeError('The `origin` argument needs to be a string or an URL object');
+ }
+
+ if (options) {
+ // Validate servername
+ const {servername} = options;
+ const {hostname} = origin;
+ if (servername && hostname !== servername) {
+ throw new Error(`Origin ${hostname} differs from servername ${servername}`);
+ }
+ }
+ } catch (error) {
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+
+ return;
+ }
+
+ const normalizedOptions = this.normalizeOptions(options);
+ const normalizedOrigin = origin.origin;
+
+ if (normalizedOptions in this.sessions) {
+ const sessions = this.sessions[normalizedOptions];
+
+ let maxConcurrentStreams = -1;
+ let currentStreamsCount = -1;
+ let optimalSession;
+
+ // We could just do this.sessions[normalizedOptions].find(...) but that isn't optimal.
+ // Additionally, we are looking for session which has biggest current pending streams count.
+ //
+ // |------------| |------------| |------------| |------------|
+ // | Session: A | | Session: B | | Session: C | | Session: D |
+ // | Pending: 5 |-| Pending: 8 |-| Pending: 9 |-| Pending: 4 |
+ // | Max: 10 | | Max: 10 | | Max: 9 | | Max: 5 |
+ // |------------| |------------| |------------| |------------|
+ // ^
+ // |
+ // pick this one --
+ //
+ for (let index = 0; index < sessions.length; index++) {
+ const session = sessions[index];
+
+ const sessionMaxConcurrentStreams = session.remoteSettings.maxConcurrentStreams;
+
+ if (sessionMaxConcurrentStreams < maxConcurrentStreams) {
+ break;
+ }
+
+ if (!session[kOriginSet].includes(normalizedOrigin)) {
+ continue;
+ }
+
+ const sessionCurrentStreamsCount = session[kCurrentStreamCount];
+
+ if (
+ sessionCurrentStreamsCount >= sessionMaxConcurrentStreams
+ || session[kGracefullyClosing]
+ // Unfortunately the `close` event isn't called immediately,
+ // so `session.destroyed` is `true`, but `session.closed` is `false`.
+ || session.destroyed
+ ) {
+ continue;
+ }
+
+ // We only need set this once.
+ if (!optimalSession) {
+ maxConcurrentStreams = sessionMaxConcurrentStreams;
+ }
+
+ // Either get the session which has biggest current stream count or the lowest.
+ if (this._isBetterSession(sessionCurrentStreamsCount, currentStreamsCount)) {
+ optimalSession = session;
+ currentStreamsCount = sessionCurrentStreamsCount;
+ }
+ }
+
+ if (optimalSession) {
+ this._accept(optimalSession, listeners, normalizedOrigin, options);
+ return;
+ }
+ }
+
+ if (normalizedOptions in this.queue) {
+ if (normalizedOrigin in this.queue[normalizedOptions]) {
+ // There's already an item in the queue, just attach ourselves to it.
+ this.queue[normalizedOptions][normalizedOrigin].listeners.push(...listeners);
+ return;
+ }
+ } else {
+ this.queue[normalizedOptions] = {
+ [kLength]: 0
+ };
+ }
+
+ // The entry must be removed from the queue IMMEDIATELY when:
+ // 1. the session connects successfully,
+ // 2. an error occurs.
+ const removeFromQueue = () => {
+ // Our entry can be replaced. We cannot remove the new one.
+ if (normalizedOptions in this.queue && this.queue[normalizedOptions][normalizedOrigin] === entry) {
+ delete this.queue[normalizedOptions][normalizedOrigin];
+
+ if (--this.queue[normalizedOptions][kLength] === 0) {
+ delete this.queue[normalizedOptions];
+ }
+ }
+ };
+
+ // The main logic is here
+ const entry = async () => {
+ this._sessionCount++;
+
+ const name = `${normalizedOrigin}:${normalizedOptions}`;
+ let receivedSettings = false;
+ let socket;
+
+ try {
+ const computedOptions = {...options};
+
+ if (computedOptions.settings === undefined) {
+ computedOptions.settings = this.settings;
+ }
+
+ if (computedOptions.session === undefined) {
+ computedOptions.session = this.tlsSessionCache.get(name);
+ }
+
+ const createConnection = computedOptions.createConnection || this.createConnection;
+
+ // A hacky workaround to enable async `createConnection`
+ socket = await createConnection.call(this, origin, computedOptions);
+ computedOptions.createConnection = () => socket;
+
+ const session = http2.connect(origin, computedOptions);
+ session[kCurrentStreamCount] = 0;
+ session[kGracefullyClosing] = false;
+
+ // Node.js return https://false:443 instead of https://1.1.1.1:443
+ const getOriginSet = () => {
+ const {socket} = session;
+
+ let originSet;
+ if (socket.servername === false) {
+ socket.servername = socket.remoteAddress;
+ originSet = session.originSet;
+ socket.servername = false;
+ } else {
+ originSet = session.originSet;
+ }
+
+ return originSet;
+ };
+
+ const isFree = () => session[kCurrentStreamCount] < session.remoteSettings.maxConcurrentStreams;
+
+ session.socket.once('session', tlsSession => {
+ this.tlsSessionCache.set(name, tlsSession);
+ });
+
+ session.once('error', error => {
+ // Listeners are empty when the session successfully connected.
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+
+ // The connection got broken, purge the cache.
+ this.tlsSessionCache.delete(name);
+ });
+
+ session.setTimeout(this.timeout, () => {
+ // Terminates all streams owned by this session.
+ session.destroy();
+ });
+
+ session.once('close', () => {
+ this._sessionCount--;
+
+ if (receivedSettings) {
+ // Assumes session `close` is emitted after request `close`
+ this._emptySessionCount--;
+
+ // This cannot be moved to the stream logic,
+ // because there may be a session that hadn't made a single request.
+ const where = this.sessions[normalizedOptions];
+
+ if (where.length === 1) {
+ delete this.sessions[normalizedOptions];
+ } else {
+ where.splice(where.indexOf(session), 1);
+ }
+ } else {
+ // Broken connection
+ removeFromQueue();
+
+ const error = new Error('Session closed without receiving a SETTINGS frame');
+ error.code = 'HTTP2WRAPPER_NOSETTINGS';
+
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+ }
+
+ // There may be another session awaiting.
+ this._processQueue();
+ });
+
+ // Iterates over the queue and processes listeners.
+ const processListeners = () => {
+ const queue = this.queue[normalizedOptions];
+ if (!queue) {
+ return;
+ }
+
+ const originSet = session[kOriginSet];
+
+ for (let index = 0; index < originSet.length; index++) {
+ const origin = originSet[index];
+
+ if (origin in queue) {
+ const {listeners, completed} = queue[origin];
+
+ let index = 0;
+
+ // Prevents session overloading.
+ while (index < listeners.length && isFree()) {
+ // We assume `resolve(...)` calls `request(...)` *directly*,
+ // otherwise the session will get overloaded.
+ listeners[index].resolve(session);
+
+ index++;
+ }
+
+ queue[origin].listeners.splice(0, index);
+
+ if (queue[origin].listeners.length === 0 && !completed) {
+ delete queue[origin];
+
+ if (--queue[kLength] === 0) {
+ delete this.queue[normalizedOptions];
+ break;
+ }
+ }
+
+ // We're no longer free, no point in continuing.
+ if (!isFree()) {
+ break;
+ }
+ }
+ }
+ };
+
+ // The Origin Set cannot shrink. No need to check if it suddenly became covered by another one.
+ session.on('origin', () => {
+ session[kOriginSet] = getOriginSet() || [];
+ session[kGracefullyClosing] = false;
+ closeSessionIfCovered(this.sessions[normalizedOptions], session);
+
+ if (session[kGracefullyClosing] || !isFree()) {
+ return;
+ }
+
+ processListeners();
+
+ if (!isFree()) {
+ return;
+ }
+
+ // Close covered sessions (if possible).
+ closeCoveredSessions(this.sessions[normalizedOptions], session);
+ });
+
+ session.once('remoteSettings', () => {
+ // The Agent could have been destroyed already.
+ if (entry.destroyed) {
+ const error = new Error('Agent has been destroyed');
+
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+
+ session.destroy();
+ return;
+ }
+
+ // See https://github.com/nodejs/node/issues/38426
+ if (session.setLocalWindowSize) {
+ session.setLocalWindowSize(1024 * 1024 * 4); // 4 MB
+ }
+
+ session[kOriginSet] = getOriginSet() || [];
+
+ if (session.socket.encrypted) {
+ const mainOrigin = session[kOriginSet][0];
+ if (mainOrigin !== normalizedOrigin) {
+ const error = new Error(`Requested origin ${normalizedOrigin} does not match server ${mainOrigin}`);
+
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+
+ session.destroy();
+ return;
+ }
+ }
+
+ removeFromQueue();
+
+ {
+ const where = this.sessions;
+
+ if (normalizedOptions in where) {
+ const sessions = where[normalizedOptions];
+ sessions.splice(getSortedIndex(sessions, session, compareSessions), 0, session);
+ } else {
+ where[normalizedOptions] = [session];
+ }
+ }
+
+ receivedSettings = true;
+ this._emptySessionCount++;
+
+ this.emit('session', session);
+ this._accept(session, listeners, normalizedOrigin, options);
+
+ if (session[kCurrentStreamCount] === 0 && this._emptySessionCount > this.maxEmptySessions) {
+ this.closeEmptySessions(this._emptySessionCount - this.maxEmptySessions);
+ }
+
+ // `session.remoteSettings.maxConcurrentStreams` might get increased
+ session.on('remoteSettings', () => {
+ if (!isFree()) {
+ return;
+ }
+
+ processListeners();
+
+ if (!isFree()) {
+ return;
+ }
+
+ // In case the Origin Set changes
+ closeCoveredSessions(this.sessions[normalizedOptions], session);
+ });
+ });
+
+ // Shim `session.request()` in order to catch all streams
+ session[kRequest] = session.request;
+ session.request = (headers, streamOptions) => {
+ if (session[kGracefullyClosing]) {
+ throw new Error('The session is gracefully closing. No new streams are allowed.');
+ }
+
+ const stream = session[kRequest](headers, streamOptions);
+
+ // The process won't exit until the session is closed or all requests are gone.
+ session.ref();
+
+ if (session[kCurrentStreamCount]++ === 0) {
+ this._emptySessionCount--;
+ }
+
+ stream.once('close', () => {
+ if (--session[kCurrentStreamCount] === 0) {
+ this._emptySessionCount++;
+ session.unref();
+
+ if (this._emptySessionCount > this.maxEmptySessions || session[kGracefullyClosing]) {
+ session.close();
+ return;
+ }
+ }
+
+ if (session.destroyed || session.closed) {
+ return;
+ }
+
+ if (isFree() && !closeSessionIfCovered(this.sessions[normalizedOptions], session)) {
+ closeCoveredSessions(this.sessions[normalizedOptions], session);
+ processListeners();
+
+ if (session[kCurrentStreamCount] === 0) {
+ this._processQueue();
+ }
+ }
+ });
+
+ return stream;
+ };
+ } catch (error) {
+ removeFromQueue();
+ this._sessionCount--;
+
+ for (let index = 0; index < listeners.length; index++) {
+ listeners[index].reject(error);
+ }
+ }
+ };
+
+ entry.listeners = listeners;
+ entry.completed = false;
+ entry.destroyed = false;
+
+ this.queue[normalizedOptions][normalizedOrigin] = entry;
+ this.queue[normalizedOptions][kLength]++;
+ this._processQueue();
+ });
+ }
+
+ request(origin, options, headers, streamOptions) {
+ return new Promise((resolve, reject) => {
+ this.getSession(origin, options, [{
+ reject,
+ resolve: session => {
+ try {
+ const stream = session.request(headers, streamOptions);
+
+ // Do not throw before `request(...)` has been awaited
+ delayAsyncDestroy(stream);
+
+ resolve(stream);
+ } catch (error) {
+ reject(error);
+ }
+ }
+ }]);
+ });
+ }
+
+ async createConnection(origin, options) {
+ return Agent.connect(origin, options);
+ }
+
+ static connect(origin, options) {
+ options.ALPNProtocols = ['h2'];
+
+ const port = origin.port || 443;
+ const host = origin.hostname;
+
+ if (typeof options.servername === 'undefined') {
+ options.servername = host;
+ }
+
+ const socket = tls.connect(port, host, options);
+
+ if (options.socket) {
+ socket._peername = {
+ family: undefined,
+ address: undefined,
+ port
+ };
+ }
+
+ return socket;
+ }
+
+ closeEmptySessions(maxCount = Number.POSITIVE_INFINITY) {
+ let closedCount = 0;
+
+ const {sessions} = this;
+
+ // eslint-disable-next-line guard-for-in
+ for (const key in sessions) {
+ const thisSessions = sessions[key];
+
+ for (let index = 0; index < thisSessions.length; index++) {
+ const session = thisSessions[index];
+
+ if (session[kCurrentStreamCount] === 0) {
+ closedCount++;
+ session.close();
+
+ if (closedCount >= maxCount) {
+ return closedCount;
+ }
+ }
+ }
+ }
+
+ return closedCount;
+ }
+
+ destroy(reason) {
+ const {sessions, queue} = this;
+
+ // eslint-disable-next-line guard-for-in
+ for (const key in sessions) {
+ const thisSessions = sessions[key];
+
+ for (let index = 0; index < thisSessions.length; index++) {
+ thisSessions[index].destroy(reason);
+ }
+ }
+
+ // eslint-disable-next-line guard-for-in
+ for (const normalizedOptions in queue) {
+ const entries = queue[normalizedOptions];
+
+ // eslint-disable-next-line guard-for-in
+ for (const normalizedOrigin in entries) {
+ entries[normalizedOrigin].destroyed = true;
+ }
+ }
+
+ // New requests should NOT attach to destroyed sessions
+ this.queue = {};
+ this.tlsSessionCache.clear();
+ }
+
+ get emptySessionCount() {
+ return this._emptySessionCount;
+ }
+
+ get pendingSessionCount() {
+ return this._sessionCount - this._emptySessionCount;
+ }
+
+ get sessionCount() {
+ return this._sessionCount;
+ }
+}
+
+Agent.kCurrentStreamCount = kCurrentStreamCount;
+Agent.kGracefullyClosing = kGracefullyClosing;
+
+module.exports = {
+ Agent,
+ globalAgent: new Agent()
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/auto.js b/includes/external/addressbook/node_modules/http2-wrapper/source/auto.js
new file mode 100644
index 0000000..6085d0d
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/auto.js
@@ -0,0 +1,206 @@
+'use strict';
+// See https://github.com/facebook/jest/issues/2549
+// eslint-disable-next-line node/prefer-global/url
+const {URL, urlToHttpOptions} = require('url');
+const http = require('http');
+const https = require('https');
+const resolveALPN = require('resolve-alpn');
+const QuickLRU = require('quick-lru');
+const {Agent, globalAgent} = require('./agent.js');
+const Http2ClientRequest = require('./client-request.js');
+const calculateServerName = require('./utils/calculate-server-name.js');
+const delayAsyncDestroy = require('./utils/delay-async-destroy.js');
+
+const cache = new QuickLRU({maxSize: 100});
+const queue = new Map();
+
+const installSocket = (agent, socket, options) => {
+ socket._httpMessage = {shouldKeepAlive: true};
+
+ const onFree = () => {
+ agent.emit('free', socket, options);
+ };
+
+ socket.on('free', onFree);
+
+ const onClose = () => {
+ agent.removeSocket(socket, options);
+ };
+
+ socket.on('close', onClose);
+
+ const onTimeout = () => {
+ const {freeSockets} = agent;
+
+ for (const sockets of Object.values(freeSockets)) {
+ if (sockets.includes(socket)) {
+ socket.destroy();
+ return;
+ }
+ }
+ };
+
+ socket.on('timeout', onTimeout);
+
+ const onRemove = () => {
+ agent.removeSocket(socket, options);
+ socket.off('close', onClose);
+ socket.off('free', onFree);
+ socket.off('timeout', onTimeout);
+ socket.off('agentRemove', onRemove);
+ };
+
+ socket.on('agentRemove', onRemove);
+
+ agent.emit('free', socket, options);
+};
+
+const createResolveProtocol = (cache, queue = new Map(), connect = undefined) => {
+ return async options => {
+ const name = `${options.host}:${options.port}:${options.ALPNProtocols.sort()}`;
+
+ if (!cache.has(name)) {
+ if (queue.has(name)) {
+ const result = await queue.get(name);
+ return {alpnProtocol: result.alpnProtocol};
+ }
+
+ const {path} = options;
+ options.path = options.socketPath;
+
+ const resultPromise = resolveALPN(options, connect);
+ queue.set(name, resultPromise);
+
+ try {
+ const result = await resultPromise;
+
+ cache.set(name, result.alpnProtocol);
+ queue.delete(name);
+
+ options.path = path;
+
+ return result;
+ } catch (error) {
+ queue.delete(name);
+
+ options.path = path;
+
+ throw error;
+ }
+ }
+
+ return {alpnProtocol: cache.get(name)};
+ };
+};
+
+const defaultResolveProtocol = createResolveProtocol(cache, queue);
+
+module.exports = async (input, options, callback) => {
+ if (typeof input === 'string') {
+ input = urlToHttpOptions(new URL(input));
+ } else if (input instanceof URL) {
+ input = urlToHttpOptions(input);
+ } else {
+ input = {...input};
+ }
+
+ if (typeof options === 'function' || options === undefined) {
+ // (options, callback)
+ callback = options;
+ options = input;
+ } else {
+ // (input, options, callback)
+ options = Object.assign(input, options);
+ }
+
+ options.ALPNProtocols = options.ALPNProtocols || ['h2', 'http/1.1'];
+
+ if (!Array.isArray(options.ALPNProtocols) || options.ALPNProtocols.length === 0) {
+ throw new Error('The `ALPNProtocols` option must be an Array with at least one entry');
+ }
+
+ options.protocol = options.protocol || 'https:';
+ const isHttps = options.protocol === 'https:';
+
+ options.host = options.hostname || options.host || 'localhost';
+ options.session = options.tlsSession;
+ options.servername = options.servername || calculateServerName((options.headers && options.headers.host) || options.host);
+ options.port = options.port || (isHttps ? 443 : 80);
+ options._defaultAgent = isHttps ? https.globalAgent : http.globalAgent;
+
+ const resolveProtocol = options.resolveProtocol || defaultResolveProtocol;
+
+ // Note: We don't support `h2session` here
+
+ let {agent} = options;
+ if (agent !== undefined && agent !== false && agent.constructor.name !== 'Object') {
+ throw new Error('The `options.agent` can be only an object `http`, `https` or `http2` properties');
+ }
+
+ if (isHttps) {
+ options.resolveSocket = true;
+
+ let {socket, alpnProtocol, timeout} = await resolveProtocol(options);
+
+ if (timeout) {
+ if (socket) {
+ socket.destroy();
+ }
+
+ const error = new Error(`Timed out resolving ALPN: ${options.timeout} ms`);
+ error.code = 'ETIMEDOUT';
+ error.ms = options.timeout;
+
+ throw error;
+ }
+
+ // We can't accept custom `createConnection` because the API is different for HTTP/2
+ if (socket && options.createConnection) {
+ socket.destroy();
+ socket = undefined;
+ }
+
+ delete options.resolveSocket;
+
+ const isHttp2 = alpnProtocol === 'h2';
+
+ if (agent) {
+ agent = isHttp2 ? agent.http2 : agent.https;
+ options.agent = agent;
+ }
+
+ if (agent === undefined) {
+ agent = isHttp2 ? globalAgent : https.globalAgent;
+ }
+
+ if (socket) {
+ if (agent === false) {
+ socket.destroy();
+ } else {
+ const defaultCreateConnection = (isHttp2 ? Agent : https.Agent).prototype.createConnection;
+
+ if (agent.createConnection === defaultCreateConnection) {
+ if (isHttp2) {
+ options._reuseSocket = socket;
+ } else {
+ installSocket(agent, socket, options);
+ }
+ } else {
+ socket.destroy();
+ }
+ }
+ }
+
+ if (isHttp2) {
+ return delayAsyncDestroy(new Http2ClientRequest(options, callback));
+ }
+ } else if (agent) {
+ options.agent = agent.http;
+ }
+
+ return delayAsyncDestroy(http.request(options, callback));
+};
+
+module.exports.protocolCache = cache;
+module.exports.resolveProtocol = defaultResolveProtocol;
+module.exports.createResolveProtocol = createResolveProtocol;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/client-request.js b/includes/external/addressbook/node_modules/http2-wrapper/source/client-request.js
new file mode 100644
index 0000000..2fd2266
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/client-request.js
@@ -0,0 +1,563 @@
+'use strict';
+// See https://github.com/facebook/jest/issues/2549
+// eslint-disable-next-line node/prefer-global/url
+const {URL, urlToHttpOptions} = require('url');
+const http2 = require('http2');
+const {Writable} = require('stream');
+const {Agent, globalAgent} = require('./agent.js');
+const IncomingMessage = require('./incoming-message.js');
+const proxyEvents = require('./utils/proxy-events.js');
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_PROTOCOL,
+ ERR_HTTP_HEADERS_SENT
+} = require('./utils/errors.js');
+const validateHeaderName = require('./utils/validate-header-name.js');
+const validateHeaderValue = require('./utils/validate-header-value.js');
+const proxySocketHandler = require('./utils/proxy-socket-handler.js');
+
+const {
+ HTTP2_HEADER_STATUS,
+ HTTP2_HEADER_METHOD,
+ HTTP2_HEADER_PATH,
+ HTTP2_HEADER_AUTHORITY,
+ HTTP2_METHOD_CONNECT
+} = http2.constants;
+
+const kHeaders = Symbol('headers');
+const kOrigin = Symbol('origin');
+const kSession = Symbol('session');
+const kOptions = Symbol('options');
+const kFlushedHeaders = Symbol('flushedHeaders');
+const kJobs = Symbol('jobs');
+const kPendingAgentPromise = Symbol('pendingAgentPromise');
+
+class ClientRequest extends Writable {
+ constructor(input, options, callback) {
+ super({
+ autoDestroy: false,
+ emitClose: false
+ });
+
+ if (typeof input === 'string') {
+ input = urlToHttpOptions(new URL(input));
+ } else if (input instanceof URL) {
+ input = urlToHttpOptions(input);
+ } else {
+ input = {...input};
+ }
+
+ if (typeof options === 'function' || options === undefined) {
+ // (options, callback)
+ callback = options;
+ options = input;
+ } else {
+ // (input, options, callback)
+ options = Object.assign(input, options);
+ }
+
+ if (options.h2session) {
+ this[kSession] = options.h2session;
+
+ if (this[kSession].destroyed) {
+ throw new Error('The session has been closed already');
+ }
+
+ this.protocol = this[kSession].socket.encrypted ? 'https:' : 'http:';
+ } else if (options.agent === false) {
+ this.agent = new Agent({maxEmptySessions: 0});
+ } else if (typeof options.agent === 'undefined' || options.agent === null) {
+ this.agent = globalAgent;
+ } else if (typeof options.agent.request === 'function') {
+ this.agent = options.agent;
+ } else {
+ throw new ERR_INVALID_ARG_TYPE('options.agent', ['http2wrapper.Agent-like Object', 'undefined', 'false'], options.agent);
+ }
+
+ if (this.agent) {
+ this.protocol = this.agent.protocol;
+ }
+
+ if (options.protocol && options.protocol !== this.protocol) {
+ throw new ERR_INVALID_PROTOCOL(options.protocol, this.protocol);
+ }
+
+ if (!options.port) {
+ options.port = options.defaultPort || (this.agent && this.agent.defaultPort) || 443;
+ }
+
+ options.host = options.hostname || options.host || 'localhost';
+
+ // Unused
+ delete options.hostname;
+
+ const {timeout} = options;
+ options.timeout = undefined;
+
+ this[kHeaders] = Object.create(null);
+ this[kJobs] = [];
+
+ this[kPendingAgentPromise] = undefined;
+
+ this.socket = null;
+ this.connection = null;
+
+ this.method = options.method || 'GET';
+
+ if (!(this.method === 'CONNECT' && (options.path === '/' || options.path === undefined))) {
+ this.path = options.path;
+ }
+
+ this.res = null;
+ this.aborted = false;
+ this.reusedSocket = false;
+
+ const {headers} = options;
+ if (headers) {
+ // eslint-disable-next-line guard-for-in
+ for (const header in headers) {
+ this.setHeader(header, headers[header]);
+ }
+ }
+
+ if (options.auth && !('authorization' in this[kHeaders])) {
+ this[kHeaders].authorization = 'Basic ' + Buffer.from(options.auth).toString('base64');
+ }
+
+ options.session = options.tlsSession;
+ options.path = options.socketPath;
+
+ this[kOptions] = options;
+
+ // Clients that generate HTTP/2 requests directly SHOULD use the :authority pseudo-header field instead of the Host header field.
+ this[kOrigin] = new URL(`${this.protocol}//${options.servername || options.host}:${options.port}`);
+
+ // A socket is being reused
+ const reuseSocket = options._reuseSocket;
+ if (reuseSocket) {
+ options.createConnection = (...args) => {
+ if (reuseSocket.destroyed) {
+ return this.agent.createConnection(...args);
+ }
+
+ return reuseSocket;
+ };
+
+ // eslint-disable-next-line promise/prefer-await-to-then
+ this.agent.getSession(this[kOrigin], this[kOptions]).catch(() => {});
+ }
+
+ if (timeout) {
+ this.setTimeout(timeout);
+ }
+
+ if (callback) {
+ this.once('response', callback);
+ }
+
+ this[kFlushedHeaders] = false;
+ }
+
+ get method() {
+ return this[kHeaders][HTTP2_HEADER_METHOD];
+ }
+
+ set method(value) {
+ if (value) {
+ this[kHeaders][HTTP2_HEADER_METHOD] = value.toUpperCase();
+ }
+ }
+
+ get path() {
+ const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
+
+ return this[kHeaders][header];
+ }
+
+ set path(value) {
+ if (value) {
+ const header = this.method === 'CONNECT' ? HTTP2_HEADER_AUTHORITY : HTTP2_HEADER_PATH;
+
+ this[kHeaders][header] = value;
+ }
+ }
+
+ get host() {
+ return this[kOrigin].hostname;
+ }
+
+ set host(_value) {
+ // Do nothing as this is read only.
+ }
+
+ get _mustNotHaveABody() {
+ return this.method === 'GET' || this.method === 'HEAD' || this.method === 'DELETE';
+ }
+
+ _write(chunk, encoding, callback) {
+ // https://github.com/nodejs/node/blob/654df09ae0c5e17d1b52a900a545f0664d8c7627/lib/internal/http2/util.js#L148-L156
+ if (this._mustNotHaveABody) {
+ callback(new Error('The GET, HEAD and DELETE methods must NOT have a body'));
+ /* istanbul ignore next: Node.js 12 throws directly */
+ return;
+ }
+
+ this.flushHeaders();
+
+ const callWrite = () => this._request.write(chunk, encoding, callback);
+ if (this._request) {
+ callWrite();
+ } else {
+ this[kJobs].push(callWrite);
+ }
+ }
+
+ _final(callback) {
+ this.flushHeaders();
+
+ const callEnd = () => {
+ // For GET, HEAD and DELETE and CONNECT
+ if (this._mustNotHaveABody || this.method === 'CONNECT') {
+ callback();
+ return;
+ }
+
+ this._request.end(callback);
+ };
+
+ if (this._request) {
+ callEnd();
+ } else {
+ this[kJobs].push(callEnd);
+ }
+ }
+
+ abort() {
+ if (this.res && this.res.complete) {
+ return;
+ }
+
+ if (!this.aborted) {
+ process.nextTick(() => this.emit('abort'));
+ }
+
+ this.aborted = true;
+
+ this.destroy();
+ }
+
+ async _destroy(error, callback) {
+ if (this.res) {
+ this.res._dump();
+ }
+
+ if (this._request) {
+ this._request.destroy();
+ } else {
+ process.nextTick(() => {
+ this.emit('close');
+ });
+ }
+
+ try {
+ await this[kPendingAgentPromise];
+ } catch (internalError) {
+ if (this.aborted) {
+ error = internalError;
+ }
+ }
+
+ callback(error);
+ }
+
+ async flushHeaders() {
+ if (this[kFlushedHeaders] || this.destroyed) {
+ return;
+ }
+
+ this[kFlushedHeaders] = true;
+
+ const isConnectMethod = this.method === HTTP2_METHOD_CONNECT;
+
+ // The real magic is here
+ const onStream = stream => {
+ this._request = stream;
+
+ if (this.destroyed) {
+ stream.destroy();
+ return;
+ }
+
+ // Forwards `timeout`, `continue`, `close` and `error` events to this instance.
+ if (!isConnectMethod) {
+ // TODO: Should we proxy `close` here?
+ proxyEvents(stream, this, ['timeout', 'continue']);
+ }
+
+ stream.once('error', error => {
+ this.destroy(error);
+ });
+
+ stream.once('aborted', () => {
+ const {res} = this;
+ if (res) {
+ res.aborted = true;
+ res.emit('aborted');
+ res.destroy();
+ } else {
+ this.destroy(new Error('The server aborted the HTTP/2 stream'));
+ }
+ });
+
+ const onResponse = (headers, flags, rawHeaders) => {
+ // If we were to emit raw request stream, it would be as fast as the native approach.
+ // Note that wrapping the raw stream in a Proxy instance won't improve the performance (already tested it).
+ const response = new IncomingMessage(this.socket, stream.readableHighWaterMark);
+ this.res = response;
+
+ // Undocumented, but it is used by `cacheable-request`
+ response.url = `${this[kOrigin].origin}${this.path}`;
+
+ response.req = this;
+ response.statusCode = headers[HTTP2_HEADER_STATUS];
+ response.headers = headers;
+ response.rawHeaders = rawHeaders;
+
+ response.once('end', () => {
+ response.complete = true;
+
+ // Has no effect, just be consistent with the Node.js behavior
+ response.socket = null;
+ response.connection = null;
+ });
+
+ if (isConnectMethod) {
+ response.upgrade = true;
+
+ // The HTTP1 API says the socket is detached here,
+ // but we can't do that so we pass the original HTTP2 request.
+ if (this.emit('connect', response, stream, Buffer.alloc(0))) {
+ this.emit('close');
+ } else {
+ // No listeners attached, destroy the original request.
+ stream.destroy();
+ }
+ } else {
+ // Forwards data
+ stream.on('data', chunk => {
+ if (!response._dumped && !response.push(chunk)) {
+ stream.pause();
+ }
+ });
+
+ stream.once('end', () => {
+ if (!this.aborted) {
+ response.push(null);
+ }
+ });
+
+ if (!this.emit('response', response)) {
+ // No listeners attached, dump the response.
+ response._dump();
+ }
+ }
+ };
+
+ // This event tells we are ready to listen for the data.
+ stream.once('response', onResponse);
+
+ // Emits `information` event
+ stream.once('headers', headers => this.emit('information', {statusCode: headers[HTTP2_HEADER_STATUS]}));
+
+ stream.once('trailers', (trailers, flags, rawTrailers) => {
+ const {res} = this;
+
+ // https://github.com/nodejs/node/issues/41251
+ if (res === null) {
+ onResponse(trailers, flags, rawTrailers);
+ return;
+ }
+
+ // Assigns trailers to the response object.
+ res.trailers = trailers;
+ res.rawTrailers = rawTrailers;
+ });
+
+ stream.once('close', () => {
+ const {aborted, res} = this;
+ if (res) {
+ if (aborted) {
+ res.aborted = true;
+ res.emit('aborted');
+ res.destroy();
+ }
+
+ const finish = () => {
+ res.emit('close');
+
+ this.destroy();
+ this.emit('close');
+ };
+
+ if (res.readable) {
+ res.once('end', finish);
+ } else {
+ finish();
+ }
+
+ return;
+ }
+
+ if (!this.destroyed) {
+ this.destroy(new Error('The HTTP/2 stream has been early terminated'));
+ this.emit('close');
+ return;
+ }
+
+ this.destroy();
+ this.emit('close');
+ });
+
+ this.socket = new Proxy(stream, proxySocketHandler);
+
+ for (const job of this[kJobs]) {
+ job();
+ }
+
+ this[kJobs].length = 0;
+
+ this.emit('socket', this.socket);
+ };
+
+ if (!(HTTP2_HEADER_AUTHORITY in this[kHeaders]) && !isConnectMethod) {
+ this[kHeaders][HTTP2_HEADER_AUTHORITY] = this[kOrigin].host;
+ }
+
+ // Makes a HTTP2 request
+ if (this[kSession]) {
+ try {
+ onStream(this[kSession].request(this[kHeaders]));
+ } catch (error) {
+ this.destroy(error);
+ }
+ } else {
+ this.reusedSocket = true;
+
+ try {
+ const promise = this.agent.request(this[kOrigin], this[kOptions], this[kHeaders]);
+ this[kPendingAgentPromise] = promise;
+
+ onStream(await promise);
+
+ this[kPendingAgentPromise] = false;
+ } catch (error) {
+ this[kPendingAgentPromise] = false;
+
+ this.destroy(error);
+ }
+ }
+ }
+
+ get connection() {
+ return this.socket;
+ }
+
+ set connection(value) {
+ this.socket = value;
+ }
+
+ getHeaderNames() {
+ return Object.keys(this[kHeaders]);
+ }
+
+ hasHeader(name) {
+ if (typeof name !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
+ }
+
+ return Boolean(this[kHeaders][name.toLowerCase()]);
+ }
+
+ getHeader(name) {
+ if (typeof name !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
+ }
+
+ return this[kHeaders][name.toLowerCase()];
+ }
+
+ get headersSent() {
+ return this[kFlushedHeaders];
+ }
+
+ removeHeader(name) {
+ if (typeof name !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
+ }
+
+ if (this.headersSent) {
+ throw new ERR_HTTP_HEADERS_SENT('remove');
+ }
+
+ delete this[kHeaders][name.toLowerCase()];
+ }
+
+ setHeader(name, value) {
+ if (this.headersSent) {
+ throw new ERR_HTTP_HEADERS_SENT('set');
+ }
+
+ validateHeaderName(name);
+ validateHeaderValue(name, value);
+
+ const lowercased = name.toLowerCase();
+
+ if (lowercased === 'connection') {
+ if (value.toLowerCase() === 'keep-alive') {
+ return;
+ }
+
+ throw new Error(`Invalid 'connection' header: ${value}`);
+ }
+
+ if (lowercased === 'host' && this.method === 'CONNECT') {
+ this[kHeaders][HTTP2_HEADER_AUTHORITY] = value;
+ } else {
+ this[kHeaders][lowercased] = value;
+ }
+ }
+
+ setNoDelay() {
+ // HTTP2 sockets cannot be malformed, do nothing.
+ }
+
+ setSocketKeepAlive() {
+ // HTTP2 sockets cannot be malformed, do nothing.
+ }
+
+ setTimeout(ms, callback) {
+ const applyTimeout = () => this._request.setTimeout(ms, callback);
+
+ if (this._request) {
+ applyTimeout();
+ } else {
+ this[kJobs].push(applyTimeout);
+ }
+
+ return this;
+ }
+
+ get maxHeadersCount() {
+ if (!this.destroyed && this._request) {
+ return this._request.session.localSettings.maxHeaderListSize;
+ }
+
+ return undefined;
+ }
+
+ set maxHeadersCount(_value) {
+ // Updating HTTP2 settings would affect all requests, do nothing.
+ }
+}
+
+module.exports = ClientRequest;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/incoming-message.js b/includes/external/addressbook/node_modules/http2-wrapper/source/incoming-message.js
new file mode 100644
index 0000000..780e051
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/incoming-message.js
@@ -0,0 +1,73 @@
+'use strict';
+const {Readable} = require('stream');
+
+class IncomingMessage extends Readable {
+ constructor(socket, highWaterMark) {
+ super({
+ emitClose: false,
+ autoDestroy: true,
+ highWaterMark
+ });
+
+ this.statusCode = null;
+ this.statusMessage = '';
+ this.httpVersion = '2.0';
+ this.httpVersionMajor = 2;
+ this.httpVersionMinor = 0;
+ this.headers = {};
+ this.trailers = {};
+ this.req = null;
+
+ this.aborted = false;
+ this.complete = false;
+ this.upgrade = null;
+
+ this.rawHeaders = [];
+ this.rawTrailers = [];
+
+ this.socket = socket;
+
+ this._dumped = false;
+ }
+
+ get connection() {
+ return this.socket;
+ }
+
+ set connection(value) {
+ this.socket = value;
+ }
+
+ _destroy(error, callback) {
+ if (!this.readableEnded) {
+ this.aborted = true;
+ }
+
+ // See https://github.com/nodejs/node/issues/35303
+ callback();
+
+ this.req._request.destroy(error);
+ }
+
+ setTimeout(ms, callback) {
+ this.req.setTimeout(ms, callback);
+ return this;
+ }
+
+ _dump() {
+ if (!this._dumped) {
+ this._dumped = true;
+
+ this.removeAllListeners('data');
+ this.resume();
+ }
+ }
+
+ _read() {
+ if (this.req) {
+ this.req._request.resume();
+ }
+ }
+}
+
+module.exports = IncomingMessage;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/index.js b/includes/external/addressbook/node_modules/http2-wrapper/source/index.js
new file mode 100644
index 0000000..7a2a49c
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/index.js
@@ -0,0 +1,50 @@
+'use strict';
+const http2 = require('http2');
+const {
+ Agent,
+ globalAgent
+} = require('./agent.js');
+const ClientRequest = require('./client-request.js');
+const IncomingMessage = require('./incoming-message.js');
+const auto = require('./auto.js');
+const {
+ HttpOverHttp2,
+ HttpsOverHttp2
+} = require('./proxies/h1-over-h2.js');
+const Http2OverHttp2 = require('./proxies/h2-over-h2.js');
+const {
+ Http2OverHttp,
+ Http2OverHttps
+} = require('./proxies/h2-over-h1.js');
+const validateHeaderName = require('./utils/validate-header-name.js');
+const validateHeaderValue = require('./utils/validate-header-value.js');
+
+const request = (url, options, callback) => new ClientRequest(url, options, callback);
+
+const get = (url, options, callback) => {
+ // eslint-disable-next-line unicorn/prevent-abbreviations
+ const req = new ClientRequest(url, options, callback);
+ req.end();
+
+ return req;
+};
+
+module.exports = {
+ ...http2,
+ ClientRequest,
+ IncomingMessage,
+ Agent,
+ globalAgent,
+ request,
+ get,
+ auto,
+ proxies: {
+ HttpOverHttp2,
+ HttpsOverHttp2,
+ Http2OverHttp2,
+ Http2OverHttp,
+ Http2OverHttps
+ },
+ validateHeaderName,
+ validateHeaderValue
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/get-auth-headers.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/get-auth-headers.js
new file mode 100644
index 0000000..364a858
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/get-auth-headers.js
@@ -0,0 +1,17 @@
+'use strict';
+
+module.exports = self => {
+ const {username, password} = self.proxyOptions.url;
+
+ if (username || password) {
+ const data = `${username}:${password}`;
+ const authorization = `Basic ${Buffer.from(data).toString('base64')}`;
+
+ return {
+ 'proxy-authorization': authorization,
+ authorization
+ };
+ }
+
+ return {};
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h1-over-h2.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h1-over-h2.js
new file mode 100644
index 0000000..15a4f78
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h1-over-h2.js
@@ -0,0 +1,90 @@
+'use strict';
+const tls = require('tls');
+const http = require('http');
+const https = require('https');
+const JSStreamSocket = require('../utils/js-stream-socket.js');
+const {globalAgent} = require('../agent.js');
+const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
+const initialize = require('./initialize.js');
+const getAuthorizationHeaders = require('./get-auth-headers.js');
+
+const createConnection = (self, options, callback) => {
+ (async () => {
+ try {
+ const {proxyOptions} = self;
+ const {url, headers, raw} = proxyOptions;
+
+ const stream = await globalAgent.request(url, proxyOptions, {
+ ...getAuthorizationHeaders(self),
+ ...headers,
+ ':method': 'CONNECT',
+ ':authority': `${options.host}:${options.port}`
+ });
+
+ stream.once('error', callback);
+ stream.once('response', headers => {
+ const statusCode = headers[':status'];
+
+ if (statusCode !== 200) {
+ callback(new UnexpectedStatusCodeError(statusCode, ''));
+ return;
+ }
+
+ const encrypted = self instanceof https.Agent;
+
+ if (raw && encrypted) {
+ options.socket = stream;
+ const secureStream = tls.connect(options);
+
+ secureStream.once('close', () => {
+ stream.destroy();
+ });
+
+ callback(null, secureStream);
+ return;
+ }
+
+ const socket = new JSStreamSocket(stream);
+ socket.encrypted = false;
+ socket._handle.getpeername = out => {
+ out.family = undefined;
+ out.address = undefined;
+ out.port = undefined;
+ };
+
+ callback(null, socket);
+ });
+ } catch (error) {
+ callback(error);
+ }
+ })();
+};
+
+class HttpOverHttp2 extends http.Agent {
+ constructor(options) {
+ super(options);
+
+ initialize(this, options.proxyOptions);
+ }
+
+ createConnection(options, callback) {
+ createConnection(this, options, callback);
+ }
+}
+
+class HttpsOverHttp2 extends https.Agent {
+ constructor(options) {
+ super(options);
+
+ initialize(this, options.proxyOptions);
+ }
+
+ createConnection(options, callback) {
+ createConnection(this, options, callback);
+ }
+}
+
+module.exports = {
+ HttpOverHttp2,
+ HttpsOverHttp2
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h1.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h1.js
new file mode 100644
index 0000000..8764f07
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h1.js
@@ -0,0 +1,48 @@
+'use strict';
+const http = require('http');
+const https = require('https');
+const Http2OverHttpX = require('./h2-over-hx.js');
+const getAuthorizationHeaders = require('./get-auth-headers.js');
+
+const getStream = request => new Promise((resolve, reject) => {
+ const onConnect = (response, socket, head) => {
+ socket.unshift(head);
+
+ request.off('error', reject);
+ resolve([socket, response.statusCode, response.statusMessage]);
+ };
+
+ request.once('error', reject);
+ request.once('connect', onConnect);
+});
+
+class Http2OverHttp extends Http2OverHttpX {
+ async _getProxyStream(authority) {
+ const {proxyOptions} = this;
+ const {url, headers} = this.proxyOptions;
+
+ const network = url.protocol === 'https:' ? https : http;
+
+ // `new URL('https://localhost/httpbin.org:443')` results in
+ // a `/httpbin.org:443` path, which has an invalid leading slash.
+ const request = network.request({
+ ...proxyOptions,
+ hostname: url.hostname,
+ port: url.port,
+ path: authority,
+ headers: {
+ ...getAuthorizationHeaders(this),
+ ...headers,
+ host: authority
+ },
+ method: 'CONNECT'
+ }).end();
+
+ return getStream(request);
+ }
+}
+
+module.exports = {
+ Http2OverHttp,
+ Http2OverHttps: Http2OverHttp
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h2.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h2.js
new file mode 100644
index 0000000..414c5e9
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-h2.js
@@ -0,0 +1,32 @@
+'use strict';
+const {globalAgent} = require('../agent.js');
+const Http2OverHttpX = require('./h2-over-hx.js');
+const getAuthorizationHeaders = require('./get-auth-headers.js');
+
+const getStatusCode = stream => new Promise((resolve, reject) => {
+ stream.once('error', reject);
+ stream.once('response', headers => {
+ stream.off('error', reject);
+ resolve(headers[':status']);
+ });
+});
+
+class Http2OverHttp2 extends Http2OverHttpX {
+ async _getProxyStream(authority) {
+ const {proxyOptions} = this;
+
+ const headers = {
+ ...getAuthorizationHeaders(this),
+ ...proxyOptions.headers,
+ ':method': 'CONNECT',
+ ':authority': authority
+ };
+
+ const stream = await globalAgent.request(proxyOptions.url, proxyOptions, headers);
+ const statusCode = await getStatusCode(stream);
+
+ return [stream, statusCode, ''];
+ }
+}
+
+module.exports = Http2OverHttp2;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-hx.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-hx.js
new file mode 100644
index 0000000..0f5a104
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/h2-over-hx.js
@@ -0,0 +1,40 @@
+'use strict';
+const {Agent} = require('../agent.js');
+const JSStreamSocket = require('../utils/js-stream-socket.js');
+const UnexpectedStatusCodeError = require('./unexpected-status-code-error.js');
+const initialize = require('./initialize.js');
+
+class Http2OverHttpX extends Agent {
+ constructor(options) {
+ super(options);
+
+ initialize(this, options.proxyOptions);
+ }
+
+ async createConnection(origin, options) {
+ const authority = `${origin.hostname}:${origin.port || 443}`;
+
+ const [stream, statusCode, statusMessage] = await this._getProxyStream(authority);
+ if (statusCode !== 200) {
+ throw new UnexpectedStatusCodeError(statusCode, statusMessage);
+ }
+
+ if (this.proxyOptions.raw) {
+ options.socket = stream;
+ } else {
+ const socket = new JSStreamSocket(stream);
+ socket.encrypted = false;
+ socket._handle.getpeername = out => {
+ out.family = undefined;
+ out.address = undefined;
+ out.port = undefined;
+ };
+
+ return socket;
+ }
+
+ return super.createConnection(origin, options);
+ }
+}
+
+module.exports = Http2OverHttpX;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/initialize.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/initialize.js
new file mode 100644
index 0000000..e4c5889
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/initialize.js
@@ -0,0 +1,21 @@
+'use strict';
+// See https://github.com/facebook/jest/issues/2549
+// eslint-disable-next-line node/prefer-global/url
+const {URL} = require('url');
+const checkType = require('../utils/check-type.js');
+
+module.exports = (self, proxyOptions) => {
+ checkType('proxyOptions', proxyOptions, ['object']);
+ checkType('proxyOptions.headers', proxyOptions.headers, ['object', 'undefined']);
+ checkType('proxyOptions.raw', proxyOptions.raw, ['boolean', 'undefined']);
+ checkType('proxyOptions.url', proxyOptions.url, [URL, 'string']);
+
+ const url = new URL(proxyOptions.url);
+
+ self.proxyOptions = {
+ raw: true,
+ ...proxyOptions,
+ headers: {...proxyOptions.headers},
+ url
+ };
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js
new file mode 100644
index 0000000..c7f0216
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/proxies/unexpected-status-code-error.js
@@ -0,0 +1,11 @@
+'use strict';
+
+class UnexpectedStatusCodeError extends Error {
+ constructor(statusCode, statusMessage = '') {
+ super(`The proxy server rejected the request with status code ${statusCode} (${statusMessage || 'empty status message'})`);
+ this.statusCode = statusCode;
+ this.statusMessage = statusMessage;
+ }
+}
+
+module.exports = UnexpectedStatusCodeError;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/calculate-server-name.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/calculate-server-name.js
new file mode 100644
index 0000000..a8ba061
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/calculate-server-name.js
@@ -0,0 +1,29 @@
+'use strict';
+const {isIP} = require('net');
+const assert = require('assert');
+
+const getHost = host => {
+ if (host[0] === '[') {
+ const idx = host.indexOf(']');
+
+ assert(idx !== -1);
+ return host.slice(1, idx);
+ }
+
+ const idx = host.indexOf(':');
+ if (idx === -1) {
+ return host;
+ }
+
+ return host.slice(0, idx);
+};
+
+module.exports = host => {
+ const servername = getHost(host);
+
+ if (isIP(servername)) {
+ return '';
+ }
+
+ return servername;
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/check-type.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/check-type.js
new file mode 100644
index 0000000..ddfefdc
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/check-type.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const checkType = (name, value, types) => {
+ const valid = types.some(type => {
+ const typeofType = typeof type;
+ if (typeofType === 'string') {
+ return typeof value === type;
+ }
+
+ return value instanceof type;
+ });
+
+ if (!valid) {
+ const names = types.map(type => typeof type === 'string' ? type : type.name);
+
+ throw new TypeError(`Expected '${name}' to be a type of ${names.join(' or ')}, got ${typeof value}`);
+ }
+};
+
+module.exports = checkType;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/delay-async-destroy.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/delay-async-destroy.js
new file mode 100644
index 0000000..53d81cf
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/delay-async-destroy.js
@@ -0,0 +1,33 @@
+'use strict';
+
+module.exports = stream => {
+ if (stream.listenerCount('error') !== 0) {
+ return stream;
+ }
+
+ stream.__destroy = stream._destroy;
+ stream._destroy = (...args) => {
+ const callback = args.pop();
+
+ stream.__destroy(...args, async error => {
+ await Promise.resolve();
+ callback(error);
+ });
+ };
+
+ const onError = error => {
+ // eslint-disable-next-line promise/prefer-await-to-then
+ Promise.resolve().then(() => {
+ stream.emit('error', error);
+ });
+ };
+
+ stream.once('error', onError);
+
+ // eslint-disable-next-line promise/prefer-await-to-then
+ Promise.resolve().then(() => {
+ stream.off('error', onError);
+ });
+
+ return stream;
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/errors.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/errors.js
new file mode 100644
index 0000000..7be9e6b
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/errors.js
@@ -0,0 +1,51 @@
+'use strict';
+/* istanbul ignore file: https://github.com/nodejs/node/blob/master/lib/internal/errors.js */
+
+const makeError = (Base, key, getMessage) => {
+ module.exports[key] = class NodeError extends Base {
+ constructor(...args) {
+ super(typeof getMessage === 'string' ? getMessage : getMessage(args));
+ this.name = `${super.name} [${key}]`;
+ this.code = key;
+ }
+ };
+};
+
+makeError(TypeError, 'ERR_INVALID_ARG_TYPE', args => {
+ const type = args[0].includes('.') ? 'property' : 'argument';
+
+ let valid = args[1];
+ const isManyTypes = Array.isArray(valid);
+
+ if (isManyTypes) {
+ valid = `${valid.slice(0, -1).join(', ')} or ${valid.slice(-1)}`;
+ }
+
+ return `The "${args[0]}" ${type} must be ${isManyTypes ? 'one of' : 'of'} type ${valid}. Received ${typeof args[2]}`;
+});
+
+makeError(TypeError, 'ERR_INVALID_PROTOCOL', args =>
+ `Protocol "${args[0]}" not supported. Expected "${args[1]}"`
+);
+
+makeError(Error, 'ERR_HTTP_HEADERS_SENT', args =>
+ `Cannot ${args[0]} headers after they are sent to the client`
+);
+
+makeError(TypeError, 'ERR_INVALID_HTTP_TOKEN', args =>
+ `${args[0]} must be a valid HTTP token [${args[1]}]`
+);
+
+makeError(TypeError, 'ERR_HTTP_INVALID_HEADER_VALUE', args =>
+ `Invalid value "${args[0]} for header "${args[1]}"`
+);
+
+makeError(TypeError, 'ERR_INVALID_CHAR', args =>
+ `Invalid character in ${args[0]} [${args[1]}]`
+);
+
+makeError(
+ Error,
+ 'ERR_HTTP2_NO_SOCKET_MANIPULATION',
+ 'HTTP/2 sockets should not be directly manipulated (e.g. read and written)'
+);
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
new file mode 100644
index 0000000..bed31cd
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/is-request-pseudo-header.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = header => {
+ switch (header) {
+ case ':method':
+ case ':scheme':
+ case ':authority':
+ case ':path':
+ return true;
+ default:
+ return false;
+ }
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/js-stream-socket.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/js-stream-socket.js
new file mode 100644
index 0000000..ac22280
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/js-stream-socket.js
@@ -0,0 +1,8 @@
+'use strict';
+const stream = require('stream');
+const tls = require('tls');
+
+// Really awesome hack.
+const JSStreamSocket = (new tls.TLSSocket(new stream.PassThrough()))._handle._parentWrap.constructor;
+
+module.exports = JSStreamSocket;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-events.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-events.js
new file mode 100644
index 0000000..35e2ae0
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-events.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = (from, to, events) => {
+ for (const event of events) {
+ from.on(event, (...args) => to.emit(event, ...args));
+ }
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js
new file mode 100644
index 0000000..89a0ac4
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/proxy-socket-handler.js
@@ -0,0 +1,102 @@
+'use strict';
+const {ERR_HTTP2_NO_SOCKET_MANIPULATION} = require('./errors.js');
+
+/* istanbul ignore file */
+/* https://github.com/nodejs/node/blob/6eec858f34a40ffa489c1ec54bb24da72a28c781/lib/internal/http2/compat.js#L195-L272 */
+
+const proxySocketHandler = {
+ has(stream, property) {
+ // Replaced [kSocket] with .socket
+ const reference = stream.session === undefined ? stream : stream.session.socket;
+ return (property in stream) || (property in reference);
+ },
+
+ get(stream, property) {
+ switch (property) {
+ case 'on':
+ case 'once':
+ case 'end':
+ case 'emit':
+ case 'destroy':
+ return stream[property].bind(stream);
+ case 'writable':
+ case 'destroyed':
+ return stream[property];
+ case 'readable':
+ if (stream.destroyed) {
+ return false;
+ }
+
+ return stream.readable;
+ case 'setTimeout': {
+ const {session} = stream;
+ if (session !== undefined) {
+ return session.setTimeout.bind(session);
+ }
+
+ return stream.setTimeout.bind(stream);
+ }
+
+ case 'write':
+ case 'read':
+ case 'pause':
+ case 'resume':
+ throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
+ default: {
+ // Replaced [kSocket] with .socket
+ const reference = stream.session === undefined ? stream : stream.session.socket;
+ const value = reference[property];
+
+ return typeof value === 'function' ? value.bind(reference) : value;
+ }
+ }
+ },
+
+ getPrototypeOf(stream) {
+ if (stream.session !== undefined) {
+ // Replaced [kSocket] with .socket
+ return Reflect.getPrototypeOf(stream.session.socket);
+ }
+
+ return Reflect.getPrototypeOf(stream);
+ },
+
+ set(stream, property, value) {
+ switch (property) {
+ case 'writable':
+ case 'readable':
+ case 'destroyed':
+ case 'on':
+ case 'once':
+ case 'end':
+ case 'emit':
+ case 'destroy':
+ stream[property] = value;
+ return true;
+ case 'setTimeout': {
+ const {session} = stream;
+ if (session === undefined) {
+ stream.setTimeout = value;
+ } else {
+ session.setTimeout = value;
+ }
+
+ return true;
+ }
+
+ case 'write':
+ case 'read':
+ case 'pause':
+ case 'resume':
+ throw new ERR_HTTP2_NO_SOCKET_MANIPULATION();
+ default: {
+ // Replaced [kSocket] with .socket
+ const reference = stream.session === undefined ? stream : stream.session.socket;
+ reference[property] = value;
+ return true;
+ }
+ }
+ }
+};
+
+module.exports = proxySocketHandler;
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-name.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-name.js
new file mode 100644
index 0000000..82cbc34
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-name.js
@@ -0,0 +1,11 @@
+'use strict';
+const {ERR_INVALID_HTTP_TOKEN} = require('./errors.js');
+const isRequestPseudoHeader = require('./is-request-pseudo-header.js');
+
+const isValidHttpToken = /^[\^`\-\w!#$%&*+.|~]+$/;
+
+module.exports = name => {
+ if (typeof name !== 'string' || (!isValidHttpToken.test(name) && !isRequestPseudoHeader(name))) {
+ throw new ERR_INVALID_HTTP_TOKEN('Header name', name);
+ }
+};
diff --git a/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-value.js b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-value.js
new file mode 100644
index 0000000..749c985
--- /dev/null
+++ b/includes/external/addressbook/node_modules/http2-wrapper/source/utils/validate-header-value.js
@@ -0,0 +1,17 @@
+'use strict';
+const {
+ ERR_HTTP_INVALID_HEADER_VALUE,
+ ERR_INVALID_CHAR
+} = require('./errors.js');
+
+const isInvalidHeaderValue = /[^\t\u0020-\u007E\u0080-\u00FF]/;
+
+module.exports = (name, value) => {
+ if (typeof value === 'undefined') {
+ throw new ERR_HTTP_INVALID_HEADER_VALUE(value, name);
+ }
+
+ if (isInvalidHeaderValue.test(value)) {
+ throw new ERR_INVALID_CHAR('header content', name);
+ }
+};