@procedures/http 0.0.0-stage → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +118 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/address.d.ts +31 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +214 -0
- package/dist/address.js.map +1 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/exchange.d.ts +102 -0
- package/dist/exchange.d.ts.map +1 -0
- package/dist/exchange.js +322 -0
- package/dist/exchange.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +41 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +205 -0
- package/dist/module.js.map +1 -0
- package/dist/parsers.d.ts +37 -0
- package/dist/parsers.d.ts.map +1 -0
- package/dist/parsers.js +175 -0
- package/dist/parsers.js.map +1 -0
- package/dist/policy.d.ts +94 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +140 -0
- package/dist/policy.js.map +1 -0
- package/dist/procedures/http/index.d.ts +5 -0
- package/dist/procedures/http/index.d.ts.map +1 -0
- package/dist/procedures/http/index.js +5 -0
- package/dist/procedures/http/index.js.map +1 -0
- package/dist/procedures/http/json.d.ts +14 -0
- package/dist/procedures/http/json.d.ts.map +1 -0
- package/dist/procedures/http/json.js +56 -0
- package/dist/procedures/http/json.js.map +1 -0
- package/dist/procedures/http/ndjson.d.ts +18 -0
- package/dist/procedures/http/ndjson.d.ts.map +1 -0
- package/dist/procedures/http/ndjson.js +65 -0
- package/dist/procedures/http/ndjson.js.map +1 -0
- package/dist/procedures/http/request.d.ts +14 -0
- package/dist/procedures/http/request.d.ts.map +1 -0
- package/dist/procedures/http/request.js +79 -0
- package/dist/procedures/http/request.js.map +1 -0
- package/dist/procedures/http/sse.d.ts +19 -0
- package/dist/procedures/http/sse.d.ts.map +1 -0
- package/dist/procedures/http/sse.js +66 -0
- package/dist/procedures/http/sse.js.map +1 -0
- package/dist/register.d.ts +6 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +8 -0
- package/dist/register.js.map +1 -0
- package/dist/scope.d.ts +53 -0
- package/dist/scope.d.ts.map +1 -0
- package/dist/scope.js +107 -0
- package/dist/scope.js.map +1 -0
- package/dist/types.d.ts +102 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/package.json +79 -3
- package/src/address.ts +206 -0
- package/src/errors.ts +44 -0
- package/src/exchange.ts +381 -0
- package/src/index.ts +11 -0
- package/src/module.ts +257 -0
- package/src/parsers.ts +184 -0
- package/src/policy.ts +192 -0
- package/src/procedures/http/index.ts +4 -0
- package/src/procedures/http/json.ts +52 -0
- package/src/procedures/http/ndjson.ts +64 -0
- package/src/procedures/http/request.ts +72 -0
- package/src/procedures/http/sse.ts +65 -0
- package/src/register.ts +10 -0
- package/src/scope.ts +129 -0
- package/src/types.ts +127 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stream parsers of the HTTP procedures: the lines of NDJSON and the events of an event
|
|
3
|
+
* stream (the `text/event-stream` format of the HTML standard). Each parser reads chunks of
|
|
4
|
+
* bytes, so a line or a character can arrive in two chunks.
|
|
5
|
+
*/
|
|
6
|
+
/** One line of an NDJSON stream. */
|
|
7
|
+
export interface NdjsonLine {
|
|
8
|
+
/** The number of the line: the first line is 1. Blank lines count. */
|
|
9
|
+
readonly line: number;
|
|
10
|
+
readonly text: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* This function gives each line of a stream of bytes that is not blank. A line ends at LF, and a
|
|
14
|
+
* CR before the LF is removed. A line of more than `maxLineBytes` bytes throws `LINE_TOO_LARGE`.
|
|
15
|
+
* The last line can end without LF. A byte order mark at the start is removed.
|
|
16
|
+
*/
|
|
17
|
+
export declare function ndjsonLines(chunks: AsyncIterable<Uint8Array | string>, maxLineBytes: number, label?: string): AsyncGenerator<NdjsonLine, void, undefined>;
|
|
18
|
+
/** One server-sent event. */
|
|
19
|
+
export interface ServerSentEvent {
|
|
20
|
+
/** The type of the event: the `event` field, or `message`. */
|
|
21
|
+
readonly event: string;
|
|
22
|
+
/** The data: the `data` fields of the event, joined with LF. */
|
|
23
|
+
readonly data: string;
|
|
24
|
+
/** The last event ID: the last `id` field of the stream, when it is not empty. */
|
|
25
|
+
readonly id?: string;
|
|
26
|
+
/** The reconnection time that the event gave with a `retry` field, in milliseconds. */
|
|
27
|
+
readonly retry?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* This function gives each event of an event stream. It obeys the parse rules of the HTML
|
|
31
|
+
* standard: a line ends at CR, LF or CRLF, a blank line sends the event, a line that starts with a
|
|
32
|
+
* colon is a comment, and an event without data is not sent. An event of more than
|
|
33
|
+
* `maxEventBytes` bytes throws `EVENT_TOO_LARGE`. At the end of the stream, an event without its
|
|
34
|
+
* blank line is not sent.
|
|
35
|
+
*/
|
|
36
|
+
export declare function sseEvents(chunks: AsyncIterable<Uint8Array | string>, maxEventBytes: number, label?: string): AsyncGenerator<ServerSentEvent, void, undefined>;
|
|
37
|
+
//# sourceMappingURL=parsers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsers.d.ts","sourceRoot":"","sources":["../src/parsers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AASD;;;;GAIG;AACH,wBAAuB,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,SAAgB,GAAG,cAAc,CAAC,UAAU,EAAE,IAAI,EAAE,SAAS,CAAC,CAuCvK;AAED,6BAA6B;AAC7B,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAuB,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC,UAAU,GAAG,MAAM,CAAC,EAAE,aAAa,EAAE,MAAM,EAAE,KAAK,SAAa,GAAG,cAAc,CAAC,eAAe,EAAE,IAAI,EAAE,SAAS,CAAC,CAgGxK"}
|
package/dist/parsers.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stream parsers of the HTTP procedures: the lines of NDJSON and the events of an event
|
|
3
|
+
* stream (the `text/event-stream` format of the HTML standard). Each parser reads chunks of
|
|
4
|
+
* bytes, so a line or a character can arrive in two chunks.
|
|
5
|
+
*/
|
|
6
|
+
import { HttpError } from "./errors.js";
|
|
7
|
+
const LF = 0x0a;
|
|
8
|
+
const CR = 0x0d;
|
|
9
|
+
function bytesOf(chunk) {
|
|
10
|
+
return typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* This function gives each line of a stream of bytes that is not blank. A line ends at LF, and a
|
|
14
|
+
* CR before the LF is removed. A line of more than `maxLineBytes` bytes throws `LINE_TOO_LARGE`.
|
|
15
|
+
* The last line can end without LF. A byte order mark at the start is removed.
|
|
16
|
+
*/
|
|
17
|
+
export async function* ndjsonLines(chunks, maxLineBytes, label = "http.ndjson") {
|
|
18
|
+
let pending = [];
|
|
19
|
+
let pendingSize = 0;
|
|
20
|
+
let number = 0;
|
|
21
|
+
const tooLarge = () => new HttpError("LINE_TOO_LARGE", `${label}: line ${number + 1} has more than ${maxLineBytes} bytes (maxLineBytes)`);
|
|
22
|
+
const finish = (last) => {
|
|
23
|
+
let bytes = pending.length === 0 ? last : Buffer.concat([...pending, last]);
|
|
24
|
+
pending = [];
|
|
25
|
+
pendingSize = 0;
|
|
26
|
+
if (bytes.length > 0 && bytes[bytes.length - 1] === CR)
|
|
27
|
+
bytes = bytes.subarray(0, bytes.length - 1);
|
|
28
|
+
if (bytes.length > maxLineBytes)
|
|
29
|
+
throw tooLarge();
|
|
30
|
+
number++;
|
|
31
|
+
let text = bytes.toString("utf8");
|
|
32
|
+
if (number === 1 && text.charCodeAt(0) === 0xfeff)
|
|
33
|
+
text = text.slice(1);
|
|
34
|
+
return text.trim() === "" ? undefined : { line: number, text };
|
|
35
|
+
};
|
|
36
|
+
for await (const chunk of chunks) {
|
|
37
|
+
const buffer = bytesOf(chunk);
|
|
38
|
+
let start = 0;
|
|
39
|
+
for (;;) {
|
|
40
|
+
const end = buffer.indexOf(LF, start);
|
|
41
|
+
if (end < 0)
|
|
42
|
+
break;
|
|
43
|
+
if (pendingSize + (end - start) > maxLineBytes + 1)
|
|
44
|
+
throw tooLarge();
|
|
45
|
+
const line = finish(buffer.subarray(start, end));
|
|
46
|
+
if (line)
|
|
47
|
+
yield line;
|
|
48
|
+
start = end + 1;
|
|
49
|
+
}
|
|
50
|
+
if (start < buffer.length) {
|
|
51
|
+
const rest = buffer.subarray(start);
|
|
52
|
+
pendingSize += rest.length;
|
|
53
|
+
// One more byte for a CR before the LF
|
|
54
|
+
if (pendingSize > maxLineBytes + 1)
|
|
55
|
+
throw tooLarge();
|
|
56
|
+
pending.push(Buffer.from(rest));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (pendingSize > 0) {
|
|
60
|
+
const line = finish(Buffer.alloc(0));
|
|
61
|
+
if (line)
|
|
62
|
+
yield line;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* This function gives each event of an event stream. It obeys the parse rules of the HTML
|
|
67
|
+
* standard: a line ends at CR, LF or CRLF, a blank line sends the event, a line that starts with a
|
|
68
|
+
* colon is a comment, and an event without data is not sent. An event of more than
|
|
69
|
+
* `maxEventBytes` bytes throws `EVENT_TOO_LARGE`. At the end of the stream, an event without its
|
|
70
|
+
* blank line is not sent.
|
|
71
|
+
*/
|
|
72
|
+
export async function* sseEvents(chunks, maxEventBytes, label = "http.sse") {
|
|
73
|
+
// The decoder removes the byte order mark and keeps a character whose bytes are in two chunks
|
|
74
|
+
const decoder = new TextDecoder("utf-8");
|
|
75
|
+
let buffer = "";
|
|
76
|
+
let skipLf = false;
|
|
77
|
+
let data = "";
|
|
78
|
+
let dataBytes = 0;
|
|
79
|
+
let eventType = "";
|
|
80
|
+
let lastEventId = "";
|
|
81
|
+
let retry;
|
|
82
|
+
const tooLarge = () => new HttpError("EVENT_TOO_LARGE", `${label}: an event has more than ${maxEventBytes} bytes (maxEventBytes)`);
|
|
83
|
+
/** The event of a blank line, or undefined. */
|
|
84
|
+
const dispatch = () => {
|
|
85
|
+
const event = data === ""
|
|
86
|
+
? undefined
|
|
87
|
+
: {
|
|
88
|
+
event: eventType === "" ? "message" : eventType,
|
|
89
|
+
data: data.endsWith("\n") ? data.slice(0, -1) : data,
|
|
90
|
+
...(lastEventId !== "" ? { id: lastEventId } : {}),
|
|
91
|
+
...(retry !== undefined ? { retry } : {}),
|
|
92
|
+
};
|
|
93
|
+
data = "";
|
|
94
|
+
dataBytes = 0;
|
|
95
|
+
eventType = "";
|
|
96
|
+
retry = undefined;
|
|
97
|
+
return event;
|
|
98
|
+
};
|
|
99
|
+
const field = (line) => {
|
|
100
|
+
if (line.startsWith(":"))
|
|
101
|
+
return;
|
|
102
|
+
const colon = line.indexOf(":");
|
|
103
|
+
const name = colon < 0 ? line : line.slice(0, colon);
|
|
104
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
105
|
+
if (value.startsWith(" "))
|
|
106
|
+
value = value.slice(1);
|
|
107
|
+
switch (name) {
|
|
108
|
+
case "event":
|
|
109
|
+
eventType = value;
|
|
110
|
+
break;
|
|
111
|
+
case "data":
|
|
112
|
+
dataBytes += Buffer.byteLength(value) + 1;
|
|
113
|
+
if (dataBytes > maxEventBytes + 1)
|
|
114
|
+
throw tooLarge();
|
|
115
|
+
data += `${value}\n`;
|
|
116
|
+
break;
|
|
117
|
+
case "id":
|
|
118
|
+
if (!value.includes("\u0000"))
|
|
119
|
+
lastEventId = value;
|
|
120
|
+
break;
|
|
121
|
+
case "retry":
|
|
122
|
+
if (/^[0-9]+$/.test(value))
|
|
123
|
+
retry = Number(value);
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const lines = function* (text, end) {
|
|
130
|
+
buffer += text;
|
|
131
|
+
let start = 0;
|
|
132
|
+
if (skipLf && buffer.length > 0) {
|
|
133
|
+
if (buffer.charCodeAt(0) === 0x0a)
|
|
134
|
+
start = 1;
|
|
135
|
+
skipLf = false;
|
|
136
|
+
}
|
|
137
|
+
for (let i = start; i < buffer.length; i++) {
|
|
138
|
+
const code = buffer.charCodeAt(i);
|
|
139
|
+
if (code !== 0x0a && code !== 0x0d)
|
|
140
|
+
continue;
|
|
141
|
+
yield buffer.slice(start, i);
|
|
142
|
+
if (code === 0x0d) {
|
|
143
|
+
if (i + 1 < buffer.length) {
|
|
144
|
+
if (buffer.charCodeAt(i + 1) === 0x0a)
|
|
145
|
+
i++;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// A CR at the end of a chunk: an LF at the start of the next chunk is the same line end
|
|
149
|
+
skipLf = true;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
start = i + 1;
|
|
153
|
+
}
|
|
154
|
+
buffer = buffer.slice(start);
|
|
155
|
+
if (!end && buffer.length > maxEventBytes)
|
|
156
|
+
throw tooLarge();
|
|
157
|
+
};
|
|
158
|
+
const feed = function* (text, end) {
|
|
159
|
+
for (const line of lines(text, end)) {
|
|
160
|
+
if (line === "") {
|
|
161
|
+
const event = dispatch();
|
|
162
|
+
if (event)
|
|
163
|
+
yield event;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
field(line);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
for await (const chunk of chunks) {
|
|
171
|
+
yield* feed(typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true }), false);
|
|
172
|
+
}
|
|
173
|
+
yield* feed(decoder.decode(), true);
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=parsers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsers.js","sourceRoot":"","sources":["../src/parsers.ts"],"names":[],"mappings":"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"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host policy of the HTTP procedures (roadmap R38): the hosts that a request can reach.
|
|
3
|
+
*
|
|
4
|
+
* - The default policy allows each public host and refuses the other addresses: loopback,
|
|
5
|
+
* private, link-local, shared, multicast and reserved (see `address.ts`).
|
|
6
|
+
* - `allow` names more hosts: a host name (`api.example.com`), a wildcard (`*.example.com`), an
|
|
7
|
+
* IP address (`127.0.0.1`, `::1`) or a CIDR range (`10.0.0.0/8`). An entry can end with a port:
|
|
8
|
+
* `localhost:8080`, `[::1]:8080`, `10.0.0.0/8:443`. Without a port, an entry allows each port.
|
|
9
|
+
* - `publicHosts: false` refuses the public hosts too: only the entries of `allow` stay.
|
|
10
|
+
*
|
|
11
|
+
* The check resolves the host name and examines each address. The connection uses only the
|
|
12
|
+
* addresses that the check allowed, so a second answer of the DNS cannot change the address
|
|
13
|
+
* (DNS rebinding). A redirect goes through the same check. A refusal is `FORBIDDEN` with the
|
|
14
|
+
* reason `host-not-allowed`.
|
|
15
|
+
*/
|
|
16
|
+
import { InvocationError, type ProcedurePath } from "@procedures/client";
|
|
17
|
+
import { type AddressRange } from "./address.js";
|
|
18
|
+
/** The reason of the `FORBIDDEN` error for a host that the policy does not allow. */
|
|
19
|
+
export declare const HOST_NOT_ALLOWED = "host-not-allowed";
|
|
20
|
+
/** One address of a host name. */
|
|
21
|
+
export interface ResolvedAddress {
|
|
22
|
+
readonly address: string;
|
|
23
|
+
readonly family: 4 | 6;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* A resolver: it gives the addresses of a host name. The default uses the resolver of the
|
|
27
|
+
* operating system (`dns.lookup`), so it reads the hosts file too. A test gives its own resolver.
|
|
28
|
+
*/
|
|
29
|
+
export type HostResolver = (hostname: string) => Promise<readonly ResolvedAddress[]>;
|
|
30
|
+
/** The options of the host policy: the options of `createHttpModule()`. */
|
|
31
|
+
export interface HostPolicyOptions {
|
|
32
|
+
/**
|
|
33
|
+
* More hosts that the procedures can reach: host names, wildcards (`*.example.com`), IP
|
|
34
|
+
* addresses and CIDR ranges, each with an optional port. An entry that names a private or a
|
|
35
|
+
* loopback address allows it.
|
|
36
|
+
*/
|
|
37
|
+
readonly allow?: readonly string[] | undefined;
|
|
38
|
+
/** False: only the hosts of `allow`. The default is true: each public host too. */
|
|
39
|
+
readonly publicHosts?: boolean | undefined;
|
|
40
|
+
/** The resolver of host names. The default is `dns.lookup` with all the addresses. */
|
|
41
|
+
readonly resolve?: HostResolver | undefined;
|
|
42
|
+
}
|
|
43
|
+
/** One entry of `allow`. */
|
|
44
|
+
type AllowEntry = {
|
|
45
|
+
readonly kind: "name";
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly port: number | undefined;
|
|
48
|
+
readonly text: string;
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: "wildcard";
|
|
51
|
+
readonly suffix: string;
|
|
52
|
+
readonly port: number | undefined;
|
|
53
|
+
readonly text: string;
|
|
54
|
+
} | {
|
|
55
|
+
readonly kind: "range";
|
|
56
|
+
readonly range: AddressRange;
|
|
57
|
+
readonly port: number | undefined;
|
|
58
|
+
readonly text: string;
|
|
59
|
+
};
|
|
60
|
+
/** A host policy: the parsed options. */
|
|
61
|
+
export interface HostPolicy {
|
|
62
|
+
readonly entries: readonly AllowEntry[];
|
|
63
|
+
readonly publicHosts: boolean;
|
|
64
|
+
readonly resolve: HostResolver;
|
|
65
|
+
}
|
|
66
|
+
/** The resolver of the operating system: each address of the name, in the order of the system. */
|
|
67
|
+
export declare const systemResolver: HostResolver;
|
|
68
|
+
/** This function makes a host policy from the options. A bad entry of `allow` throws. */
|
|
69
|
+
export declare function createHostPolicy(options?: HostPolicyOptions): HostPolicy;
|
|
70
|
+
/** The default policy: each public host, and no other host. */
|
|
71
|
+
export declare const DEFAULT_HOST_POLICY: HostPolicy;
|
|
72
|
+
/** The host of a URL to connect to, after the check. */
|
|
73
|
+
export interface AllowedTarget {
|
|
74
|
+
/** The host name of the URL, or the IP address without brackets. */
|
|
75
|
+
readonly hostname: string;
|
|
76
|
+
readonly port: number;
|
|
77
|
+
/** The addresses that the connection can use: each one passed the check. */
|
|
78
|
+
readonly addresses: readonly ResolvedAddress[];
|
|
79
|
+
}
|
|
80
|
+
/** The `FORBIDDEN` error for a host that the policy does not allow. */
|
|
81
|
+
export declare function hostNotAllowed(procedure: ProcedurePath, message: string): InvocationError;
|
|
82
|
+
/** True for the `FORBIDDEN` error of a host that the policy does not allow. */
|
|
83
|
+
export declare function isHostNotAllowed(error: unknown): boolean;
|
|
84
|
+
/** The default port of a URL protocol. */
|
|
85
|
+
export declare function portOf(url: URL): number;
|
|
86
|
+
/**
|
|
87
|
+
* This function examines the host of a URL. It gives the addresses that the connection can use,
|
|
88
|
+
* or it throws `FORBIDDEN` with the reason `host-not-allowed`. A name that `allow` names keeps
|
|
89
|
+
* each address. For another name, each address must be public or in a range of `allow`. The
|
|
90
|
+
* connection uses only the addresses that pass, and the check fails when none passes.
|
|
91
|
+
*/
|
|
92
|
+
export declare function checkHost(policy: HostPolicy, url: URL, procedure: ProcedurePath, what?: string): Promise<AllowedTarget>;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host policy of the HTTP procedures (roadmap R38): the hosts that a request can reach.
|
|
3
|
+
*
|
|
4
|
+
* - The default policy allows each public host and refuses the other addresses: loopback,
|
|
5
|
+
* private, link-local, shared, multicast and reserved (see `address.ts`).
|
|
6
|
+
* - `allow` names more hosts: a host name (`api.example.com`), a wildcard (`*.example.com`), an
|
|
7
|
+
* IP address (`127.0.0.1`, `::1`) or a CIDR range (`10.0.0.0/8`). An entry can end with a port:
|
|
8
|
+
* `localhost:8080`, `[::1]:8080`, `10.0.0.0/8:443`. Without a port, an entry allows each port.
|
|
9
|
+
* - `publicHosts: false` refuses the public hosts too: only the entries of `allow` stay.
|
|
10
|
+
*
|
|
11
|
+
* The check resolves the host name and examines each address. The connection uses only the
|
|
12
|
+
* addresses that the check allowed, so a second answer of the DNS cannot change the address
|
|
13
|
+
* (DNS rebinding). A redirect goes through the same check. A refusal is `FORBIDDEN` with the
|
|
14
|
+
* reason `host-not-allowed`.
|
|
15
|
+
*/
|
|
16
|
+
import { lookup } from "node:dns/promises";
|
|
17
|
+
import { InvocationError } from "@procedures/client";
|
|
18
|
+
import { formatAddress, parseAddress, parseRange, rangeHas, specialClass } from "./address.js";
|
|
19
|
+
/** The reason of the `FORBIDDEN` error for a host that the policy does not allow. */
|
|
20
|
+
export const HOST_NOT_ALLOWED = "host-not-allowed";
|
|
21
|
+
/** The resolver of the operating system: each address of the name, in the order of the system. */
|
|
22
|
+
export const systemResolver = async (hostname) => {
|
|
23
|
+
const found = await lookup(hostname, { all: true, verbatim: true });
|
|
24
|
+
return found.map((entry) => ({ address: entry.address, family: entry.family === 6 ? 6 : 4 }));
|
|
25
|
+
};
|
|
26
|
+
/** A host name in the form of a comparison: lowercase, without a final dot. */
|
|
27
|
+
function normalName(name) {
|
|
28
|
+
return name.toLowerCase().replace(/\.$/, "");
|
|
29
|
+
}
|
|
30
|
+
const NAME = /^[a-z0-9_]([a-z0-9_-]*[a-z0-9_])?(\.[a-z0-9_]([a-z0-9_-]*[a-z0-9_])?)*$/;
|
|
31
|
+
/** This function parses one entry of `allow`. A bad entry throws, so a host finds it at the start. */
|
|
32
|
+
function parseEntry(text) {
|
|
33
|
+
let head = text.trim();
|
|
34
|
+
let port;
|
|
35
|
+
// A port: "host:80", "[::1]:80", "10.0.0.0/8:80". A bare IPv6 address has no port.
|
|
36
|
+
const withPort = /^(.*[^:]):([0-9]{1,5})$/.exec(head);
|
|
37
|
+
if (withPort && (!withPort[1].includes(":") || withPort[1].endsWith("]"))) {
|
|
38
|
+
head = withPort[1];
|
|
39
|
+
port = Number(withPort[2]);
|
|
40
|
+
if (port < 1 || port > 65535)
|
|
41
|
+
throw new Error(`The port of the allow entry "${text}" is not in 1 to 65535`);
|
|
42
|
+
}
|
|
43
|
+
const range = parseRange(head);
|
|
44
|
+
if (range)
|
|
45
|
+
return { kind: "range", range, port, text };
|
|
46
|
+
const name = normalName(head);
|
|
47
|
+
if (name.startsWith("*.") && NAME.test(name.slice(2)))
|
|
48
|
+
return { kind: "wildcard", suffix: name.slice(1), port, text };
|
|
49
|
+
if (NAME.test(name))
|
|
50
|
+
return { kind: "name", name, port, text };
|
|
51
|
+
throw new Error(`The allow entry "${text}" is not a host name, a wildcard, an IP address or a CIDR range`);
|
|
52
|
+
}
|
|
53
|
+
/** This function makes a host policy from the options. A bad entry of `allow` throws. */
|
|
54
|
+
export function createHostPolicy(options = {}) {
|
|
55
|
+
return {
|
|
56
|
+
entries: (options.allow ?? []).map(parseEntry),
|
|
57
|
+
publicHosts: options.publicHosts ?? true,
|
|
58
|
+
resolve: options.resolve ?? systemResolver,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** The default policy: each public host, and no other host. */
|
|
62
|
+
export const DEFAULT_HOST_POLICY = createHostPolicy();
|
|
63
|
+
/** The `FORBIDDEN` error for a host that the policy does not allow. */
|
|
64
|
+
export function hostNotAllowed(procedure, message) {
|
|
65
|
+
return new InvocationError("FORBIDDEN", `${procedure.join(".")}: ${message}`, procedure, HOST_NOT_ALLOWED);
|
|
66
|
+
}
|
|
67
|
+
/** True for the `FORBIDDEN` error of a host that the policy does not allow. */
|
|
68
|
+
export function isHostNotAllowed(error) {
|
|
69
|
+
return error instanceof InvocationError && error.code === "FORBIDDEN" && error.reason === HOST_NOT_ALLOWED;
|
|
70
|
+
}
|
|
71
|
+
function portMatches(entry, port) {
|
|
72
|
+
return entry.port === undefined || entry.port === port;
|
|
73
|
+
}
|
|
74
|
+
function nameAllowed(policy, hostname, port) {
|
|
75
|
+
const name = normalName(hostname);
|
|
76
|
+
return policy.entries.some((entry) => portMatches(entry, port) &&
|
|
77
|
+
((entry.kind === "name" && entry.name === name) || (entry.kind === "wildcard" && name.endsWith(entry.suffix) && name.length > entry.suffix.length)));
|
|
78
|
+
}
|
|
79
|
+
/** Undefined when the policy allows the address, or the reason of the refusal. */
|
|
80
|
+
function addressRefusal(policy, address, port) {
|
|
81
|
+
if (policy.entries.some((entry) => entry.kind === "range" && portMatches(entry, port) && rangeHas(entry.range, address)))
|
|
82
|
+
return undefined;
|
|
83
|
+
const special = specialClass(address);
|
|
84
|
+
if (special !== undefined)
|
|
85
|
+
return `${formatAddress(address)} is a ${special} address`;
|
|
86
|
+
return policy.publicHosts ? undefined : `${formatAddress(address)} is not in the allowed hosts`;
|
|
87
|
+
}
|
|
88
|
+
/** The default port of a URL protocol. */
|
|
89
|
+
export function portOf(url) {
|
|
90
|
+
if (url.port !== "")
|
|
91
|
+
return Number(url.port);
|
|
92
|
+
return url.protocol === "https:" ? 443 : 80;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* This function examines the host of a URL. It gives the addresses that the connection can use,
|
|
96
|
+
* or it throws `FORBIDDEN` with the reason `host-not-allowed`. A name that `allow` names keeps
|
|
97
|
+
* each address. For another name, each address must be public or in a range of `allow`. The
|
|
98
|
+
* connection uses only the addresses that pass, and the check fails when none passes.
|
|
99
|
+
*/
|
|
100
|
+
export async function checkHost(policy, url, procedure, what = "the URL") {
|
|
101
|
+
const hostname = url.hostname.startsWith("[") ? url.hostname.slice(1, -1) : url.hostname;
|
|
102
|
+
const port = portOf(url);
|
|
103
|
+
const literal = parseAddress(hostname);
|
|
104
|
+
if (literal) {
|
|
105
|
+
const refusal = addressRefusal(policy, literal, port);
|
|
106
|
+
if (refusal !== undefined)
|
|
107
|
+
throw hostNotAllowed(procedure, `the host of ${what} is not allowed: ${refusal}`);
|
|
108
|
+
return { hostname, port, addresses: [{ address: formatAddress(literal), family: literal.length === 4 ? 4 : 6 }] };
|
|
109
|
+
}
|
|
110
|
+
if (!policy.publicHosts && !nameAllowed(policy, hostname, port) && !policy.entries.some((entry) => entry.kind === "range")) {
|
|
111
|
+
throw hostNotAllowed(procedure, `the host ${hostname}:${port} of ${what} is not in the allowed hosts`);
|
|
112
|
+
}
|
|
113
|
+
let resolved;
|
|
114
|
+
try {
|
|
115
|
+
resolved = await policy.resolve(hostname);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
const code = error?.code;
|
|
119
|
+
throw new Error(`${procedure.join(".")}: the host ${hostname} of ${what} has no address${code ? ` (${code})` : ""}`, { cause: error });
|
|
120
|
+
}
|
|
121
|
+
if (resolved.length === 0)
|
|
122
|
+
throw new Error(`${procedure.join(".")}: the host ${hostname} of ${what} has no address`);
|
|
123
|
+
if (nameAllowed(policy, hostname, port))
|
|
124
|
+
return { hostname, port, addresses: resolved };
|
|
125
|
+
const allowed = [];
|
|
126
|
+
const refusals = [];
|
|
127
|
+
for (const entry of resolved) {
|
|
128
|
+
const bytes = parseAddress(entry.address);
|
|
129
|
+
const refusal = bytes ? addressRefusal(policy, bytes, port) : `${entry.address} is not an IP address`;
|
|
130
|
+
if (refusal === undefined)
|
|
131
|
+
allowed.push(entry);
|
|
132
|
+
else
|
|
133
|
+
refusals.push(refusal);
|
|
134
|
+
}
|
|
135
|
+
if (allowed.length === 0) {
|
|
136
|
+
throw hostNotAllowed(procedure, `the host ${hostname} of ${what} is not allowed: ${refusals.join(", ")}`);
|
|
137
|
+
}
|
|
138
|
+
return { hostname, port, addresses: allowed };
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.js","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/procedures/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/procedures/http/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http.json procedure
|
|
3
|
+
*
|
|
4
|
+
* Send a GET or a POST of JSON and give the parsed JSON response. By default, a status outside
|
|
5
|
+
* 200 to 299 is an error (`HTTP_STATUS`) with the start of the body in its message.
|
|
6
|
+
*/
|
|
7
|
+
import { type HttpSettings } from "../../exchange.js";
|
|
8
|
+
import { type ScopeContext } from "../../scope.js";
|
|
9
|
+
import type { HttpJsonInput, HttpJsonOutput } from "../../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Send a GET or a POST of JSON and give the parsed JSON response
|
|
12
|
+
*/
|
|
13
|
+
export declare function httpJson(input: HttpJsonInput, ctx?: ScopeContext, settings?: HttpSettings): Promise<HttpJsonOutput>;
|
|
14
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../../src/procedures/http/json.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAqG,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACzJ,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAIpE;;GAEG;AACH,wBAAsB,QAAQ,CAAC,KAAK,EAAE,aAAa,EAAE,GAAG,GAAE,YAAiB,EAAE,QAAQ,GAAE,YAA+B,GAAG,OAAO,CAAC,cAAc,CAAC,CAkC/I"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http.json procedure
|
|
3
|
+
*
|
|
4
|
+
* Send a GET or a POST of JSON and give the parsed JSON response. By default, a status outside
|
|
5
|
+
* 200 to 299 is an error (`HTTP_STATUS`) with the start of the body in its message.
|
|
6
|
+
*/
|
|
7
|
+
import { HttpError } from "../../errors.js";
|
|
8
|
+
import { DEFAULT_SETTINGS, DEFAULT_TIMEOUT_MS, excerpt, exchange, isOk, prepare, readBody, responseHeaders } from "../../exchange.js";
|
|
9
|
+
import { CallScope } from "../../scope.js";
|
|
10
|
+
const PATH = ["http", "json"];
|
|
11
|
+
/**
|
|
12
|
+
* Send a GET or a POST of JSON and give the parsed JSON response
|
|
13
|
+
*/
|
|
14
|
+
export async function httpJson(input, ctx = {}, settings = DEFAULT_SETTINGS) {
|
|
15
|
+
const method = input.method ?? (input.json !== undefined ? "POST" : "GET");
|
|
16
|
+
if (method === "GET" && input.json !== undefined)
|
|
17
|
+
throw new Error("http.json: a GET request has no body: use the method POST");
|
|
18
|
+
const body = input.json !== undefined ? Buffer.from(JSON.stringify(input.json), "utf8") : undefined;
|
|
19
|
+
const headers = { accept: "application/json" };
|
|
20
|
+
if (body !== undefined)
|
|
21
|
+
headers["content-type"] = "application/json";
|
|
22
|
+
const prepared = prepare({ ...input, method }, PATH, settings, { headers, body });
|
|
23
|
+
const scope = new CallScope(PATH, ctx, { timeoutMs: input.timeoutMs ?? DEFAULT_TIMEOUT_MS });
|
|
24
|
+
try {
|
|
25
|
+
const opened = await exchange(prepared, settings.policy, scope);
|
|
26
|
+
const status = opened.response.statusCode ?? 0;
|
|
27
|
+
const limit = Math.min(input.maxResponseBytes ?? settings.maxResponseBytes, settings.maxResponseBytes);
|
|
28
|
+
const bytes = await readBody(opened.body, limit, scope, opened.response);
|
|
29
|
+
const text = bytes.toString("utf8");
|
|
30
|
+
if (!isOk(status) && (input.failOnStatus ?? true)) {
|
|
31
|
+
const reason = opened.response.statusMessage ? ` ${opened.response.statusMessage}` : "";
|
|
32
|
+
throw new HttpError("HTTP_STATUS", `http.json: the server answered ${status}${reason}${text ? `: ${excerpt(text)}` : ""}`, status);
|
|
33
|
+
}
|
|
34
|
+
let data = null;
|
|
35
|
+
if (text.trim() !== "") {
|
|
36
|
+
try {
|
|
37
|
+
data = JSON.parse(text);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// An error page is often HTML: with failOnStatus: false, the output keeps its text
|
|
41
|
+
if (!isOk(status))
|
|
42
|
+
data = text;
|
|
43
|
+
else
|
|
44
|
+
throw new HttpError("BAD_JSON", `http.json: the body of the response is not JSON: ${excerpt(text)}`, status);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { status, ok: isOk(status), url: opened.url, headers: responseHeaders(opened.response.headers), data };
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
throw scope.translate(error);
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
scope.dispose();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../../src/procedures/http/json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* http.ndjson procedure
|
|
3
|
+
*
|
|
4
|
+
* Read an NDJSON response as a stream of parsed lines. The procedure sends a GET. Each line that
|
|
5
|
+
* is not blank is one item: its JSON value. The stream can run without an end, so the procedure
|
|
6
|
+
* declares an unbounded stream (roadmap R15). When the reader stops, the connection closes.
|
|
7
|
+
*
|
|
8
|
+
* The call waits for the status of the response. A refused host, a failed connection and a status
|
|
9
|
+
* outside 200 to 299 fail the call itself, before the first item.
|
|
10
|
+
*/
|
|
11
|
+
import { type HttpSettings } from "../../exchange.js";
|
|
12
|
+
import { type ScopeContext } from "../../scope.js";
|
|
13
|
+
import type { HttpNdjsonInput, HttpNdjsonOutput } from "../../types.js";
|
|
14
|
+
/**
|
|
15
|
+
* Read an NDJSON response as a stream of parsed lines
|
|
16
|
+
*/
|
|
17
|
+
export declare function httpNdjson(input: HttpNdjsonInput, ctx?: ScopeContext, settings?: HttpSettings): Promise<AsyncGenerator<HttpNdjsonOutput, void, undefined>>;
|
|
18
|
+
//# sourceMappingURL=ndjson.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ndjson.d.ts","sourceRoot":"","sources":["../../../src/procedures/http/ndjson.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAgH,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEpK,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AA6BxE;;GAEG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,GAAE,YAAiB,EAAE,QAAQ,GAAE,YAA+B,GAAG,OAAO,CAAC,cAAc,CAAC,gBAAgB,EAAE,IAAI,EAAE,SAAS,CAAC,CAAC,CAgBtL"}
|