goscript 0.2.5 → 0.2.7
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/cmd/goscript/cmd-compile.go +7 -0
- package/cmd/goscript/cmd_compile_test.go +83 -0
- package/compiler/compile-request.go +3 -0
- package/compiler/compiler-cache.go +828 -0
- package/compiler/compiler-cache_test.go +705 -0
- package/compiler/config.go +2 -0
- package/compiler/index.test.ts +26 -1
- package/compiler/index.ts +5 -0
- package/compiler/lowered-program.go +31 -20
- package/compiler/lowering.go +349 -93
- package/compiler/lowering_bench_test.go +1 -0
- package/compiler/override-facts.go +309 -8
- package/compiler/override-parity-verifier.go +45 -1
- package/compiler/override-parity-verifier_test.go +100 -0
- package/compiler/override-registry_test.go +1 -0
- package/compiler/package-graph.go +40 -12
- package/compiler/package-graph_test.go +29 -0
- package/compiler/runtime-contract.go +8 -0
- package/compiler/service.go +98 -11
- package/compiler/skeleton_test.go +110 -14
- package/compiler/typescript-emitter.go +120 -23
- package/dist/compiler/index.d.ts +2 -0
- package/dist/compiler/index.js +3 -0
- package/dist/compiler/index.js.map +1 -1
- package/dist/gs/builtin/builtin.d.ts +24 -33
- package/dist/gs/builtin/builtin.js +54 -61
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/hostio.d.ts +1 -0
- package/dist/gs/builtin/hostio.js +1 -1
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/index.d.ts +1 -0
- package/dist/gs/builtin/index.js +1 -0
- package/dist/gs/builtin/index.js.map +1 -1
- package/dist/gs/builtin/panic.d.ts +18 -0
- package/dist/gs/builtin/panic.js +98 -0
- package/dist/gs/builtin/panic.js.map +1 -0
- package/dist/gs/builtin/slice.d.ts +10 -0
- package/dist/gs/builtin/slice.js +110 -53
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +15 -3
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/builtin/varRef.d.ts +1 -1
- package/dist/gs/builtin/varRef.js +3 -2
- package/dist/gs/builtin/varRef.js.map +1 -1
- package/dist/gs/bytes/bytes.gs.js +51 -38
- package/dist/gs/bytes/bytes.gs.js.map +1 -1
- package/dist/gs/bytes/reader.gs.d.ts +1 -1
- package/dist/gs/bytes/reader.gs.js +6 -7
- package/dist/gs/bytes/reader.gs.js.map +1 -1
- package/dist/gs/cmp/index.d.ts +1 -1
- package/dist/gs/cmp/index.js +43 -10
- package/dist/gs/cmp/index.js.map +1 -1
- package/dist/gs/context/context.d.ts +2 -2
- package/dist/gs/context/context.js +1 -1
- package/dist/gs/context/context.js.map +1 -1
- package/dist/gs/embed/index.js +1 -1
- package/dist/gs/embed/index.js.map +1 -1
- package/dist/gs/encoding/binary/index.js +201 -8
- package/dist/gs/encoding/binary/index.js.map +1 -1
- package/dist/gs/encoding/json/index.d.ts +5 -0
- package/dist/gs/encoding/json/index.js +388 -25
- package/dist/gs/encoding/json/index.js.map +1 -1
- package/dist/gs/errors/errors.js +17 -24
- package/dist/gs/errors/errors.js.map +1 -1
- package/dist/gs/fmt/fmt.js +129 -35
- package/dist/gs/fmt/fmt.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
- package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
- package/dist/gs/internal/bytealg/index.js +43 -8
- package/dist/gs/internal/bytealg/index.js.map +1 -1
- package/dist/gs/internal/byteorder/index.d.ts +2 -2
- package/dist/gs/internal/byteorder/index.js +2 -2
- package/dist/gs/internal/byteorder/index.js.map +1 -1
- package/dist/gs/io/fs/format.js +2 -2
- package/dist/gs/io/fs/format.js.map +1 -1
- package/dist/gs/io/fs/fs.d.ts +1 -1
- package/dist/gs/io/fs/fs.js +1 -1
- package/dist/gs/io/fs/fs.js.map +1 -1
- package/dist/gs/io/io.d.ts +21 -21
- package/dist/gs/io/io.js +49 -50
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/math/bits/index.js +26 -8
- package/dist/gs/math/bits/index.js.map +1 -1
- package/dist/gs/math/copysign.gs.js +10 -17
- package/dist/gs/math/copysign.gs.js.map +1 -1
- package/dist/gs/math/pow.gs.js +5 -0
- package/dist/gs/math/pow.gs.js.map +1 -1
- package/dist/gs/math/signbit.gs.js +6 -2
- package/dist/gs/math/signbit.gs.js.map +1 -1
- package/dist/gs/mime/index.js +1 -0
- package/dist/gs/mime/index.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +6 -6
- package/dist/gs/net/http/index.js +507 -43
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/os/stat.gs.d.ts +2 -2
- package/dist/gs/os/types.gs.d.ts +1 -1
- package/dist/gs/os/types.gs.js +1 -1
- package/dist/gs/os/types.gs.js.map +1 -1
- package/dist/gs/os/types_js.gs.d.ts +1 -1
- package/dist/gs/os/types_js.gs.js +7 -7
- package/dist/gs/os/types_js.gs.js.map +1 -1
- package/dist/gs/os/types_unix.gs.d.ts +1 -1
- package/dist/gs/os/types_unix.gs.js +1 -1
- package/dist/gs/os/types_unix.gs.js.map +1 -1
- package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
- package/dist/gs/os/zero_copy_posix.gs.js +1 -1
- package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
- package/dist/gs/path/filepath/match.js +8 -4
- package/dist/gs/path/filepath/match.js.map +1 -1
- package/dist/gs/path/filepath/path.js +216 -42
- package/dist/gs/path/filepath/path.js.map +1 -1
- package/dist/gs/path/match.js +6 -3
- package/dist/gs/path/match.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +5 -4
- package/dist/gs/reflect/type.js +29 -11
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/slices/slices.js +11 -11
- package/dist/gs/slices/slices.js.map +1 -1
- package/dist/gs/strconv/atof.gs.js +156 -43
- package/dist/gs/strconv/atof.gs.js.map +1 -1
- package/dist/gs/strconv/atoi.gs.d.ts +3 -2
- package/dist/gs/strconv/atoi.gs.js +86 -67
- package/dist/gs/strconv/atoi.gs.js.map +1 -1
- package/dist/gs/strconv/ftoa.gs.js +73 -3
- package/dist/gs/strconv/ftoa.gs.js.map +1 -1
- package/dist/gs/strconv/itoa.gs.d.ts +4 -4
- package/dist/gs/strconv/itoa.gs.js +5 -4
- package/dist/gs/strconv/itoa.gs.js.map +1 -1
- package/dist/gs/strconv/quote.gs.d.ts +1 -1
- package/dist/gs/strconv/quote.gs.js +311 -103
- package/dist/gs/strconv/quote.gs.js.map +1 -1
- package/dist/gs/strings/reader.d.ts +1 -1
- package/dist/gs/strings/reader.js +8 -8
- package/dist/gs/strings/reader.js.map +1 -1
- package/dist/gs/strings/strings.js +87 -61
- package/dist/gs/strings/strings.js.map +1 -1
- package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
- package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
- package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
- package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
- package/dist/gs/sync/atomic/type.gs.js +4 -4
- package/dist/gs/sync/atomic/type.gs.js.map +1 -1
- package/dist/gs/sync/sync.js +50 -12
- package/dist/gs/sync/sync.js.map +1 -1
- package/dist/gs/syscall/fs.d.ts +6 -6
- package/dist/gs/syscall/fs.js +1 -1
- package/dist/gs/syscall/fs.js.map +1 -1
- package/dist/gs/time/time.d.ts +18 -18
- package/dist/gs/time/time.js +58 -55
- package/dist/gs/time/time.js.map +1 -1
- package/dist/gs/unicode/tables.d.ts +11 -0
- package/dist/gs/unicode/tables.js +635 -0
- package/dist/gs/unicode/tables.js.map +1 -0
- package/dist/gs/unicode/unicode.d.ts +58 -38
- package/dist/gs/unicode/unicode.js +362 -278
- package/dist/gs/unicode/unicode.js.map +1 -1
- package/go.sum +13 -0
- package/gs/builtin/builtin.ts +83 -93
- package/gs/builtin/hostio.ts +1 -1
- package/gs/builtin/index.ts +1 -0
- package/gs/builtin/panic.test.ts +189 -0
- package/gs/builtin/panic.ts +107 -0
- package/gs/builtin/runtime-contract.test.ts +5 -5
- package/gs/builtin/slice.test.ts +23 -0
- package/gs/builtin/slice.ts +133 -95
- package/gs/builtin/type.ts +16 -3
- package/gs/builtin/varRef.ts +4 -2
- package/gs/builtin/wide-int.test.ts +41 -0
- package/gs/bytes/bytes.gs.ts +54 -41
- package/gs/bytes/bytes.test.ts +18 -1
- package/gs/bytes/reader.gs.ts +7 -8
- package/gs/cmp/index.test.ts +55 -0
- package/gs/cmp/index.ts +45 -9
- package/gs/context/context.ts +3 -3
- package/gs/embed/index.ts +2 -2
- package/gs/encoding/binary/index.test.ts +104 -0
- package/gs/encoding/binary/index.ts +259 -11
- package/gs/encoding/json/index.test.ts +107 -0
- package/gs/encoding/json/index.ts +400 -29
- package/gs/errors/errors.test.ts +44 -1
- package/gs/errors/errors.ts +15 -31
- package/gs/fmt/fmt.test.ts +70 -2
- package/gs/fmt/fmt.ts +128 -34
- package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
- package/gs/internal/bytealg/index.test.ts +26 -1
- package/gs/internal/bytealg/index.ts +44 -8
- package/gs/internal/byteorder/index.ts +6 -4
- package/gs/io/fs/format.ts +2 -2
- package/gs/io/fs/fs.ts +2 -2
- package/gs/io/fs/stat.test.ts +2 -2
- package/gs/io/fs/sub.test.ts +2 -2
- package/gs/io/fs/walk.test.ts +2 -2
- package/gs/io/io.test.ts +47 -5
- package/gs/io/io.ts +73 -73
- package/gs/io/limit.test.ts +103 -0
- package/gs/math/bits/index.test.ts +128 -0
- package/gs/math/bits/index.ts +26 -8
- package/gs/math/copysign.gs.test.ts +3 -1
- package/gs/math/copysign.gs.ts +10 -22
- package/gs/math/pow.gs.test.ts +4 -5
- package/gs/math/pow.gs.ts +5 -0
- package/gs/math/signbit.gs.test.ts +2 -1
- package/gs/math/signbit.gs.ts +6 -3
- package/gs/mime/index.ts +1 -0
- package/gs/net/http/index.test.ts +683 -2
- package/gs/net/http/index.ts +598 -57
- package/gs/net/http/meta.json +3 -0
- package/gs/os/stat.gs.ts +2 -2
- package/gs/os/types.gs.ts +2 -2
- package/gs/os/types_js.gs.ts +9 -9
- package/gs/os/types_unix.gs.ts +2 -2
- package/gs/os/zero_copy_posix.gs.ts +2 -2
- package/gs/path/filepath/match.test.ts +16 -0
- package/gs/path/filepath/match.ts +8 -4
- package/gs/path/filepath/path.test.ts +91 -9
- package/gs/path/filepath/path.ts +223 -49
- package/gs/path/match.test.ts +32 -0
- package/gs/path/match.ts +6 -3
- package/gs/reflect/deepequal.test.ts +1 -1
- package/gs/reflect/field.test.ts +1 -1
- package/gs/reflect/function-types.test.ts +6 -6
- package/gs/reflect/sliceat.test.ts +13 -13
- package/gs/reflect/structof.test.ts +4 -4
- package/gs/reflect/type.ts +34 -14
- package/gs/reflect/typefor.test.ts +5 -5
- package/gs/runtime/pprof/index.test.ts +20 -0
- package/gs/runtime/trace/index.test.ts +3 -0
- package/gs/slices/slices.test.ts +31 -0
- package/gs/slices/slices.ts +11 -11
- package/gs/strconv/append.test.ts +99 -0
- package/gs/strconv/atof.gs.ts +156 -42
- package/gs/strconv/atof.test.ts +45 -0
- package/gs/strconv/atoi.gs.ts +87 -69
- package/gs/strconv/atoi.test.ts +49 -0
- package/gs/strconv/ftoa.gs.ts +85 -10
- package/gs/strconv/ftoa.test.ts +43 -0
- package/gs/strconv/itoa.gs.ts +10 -9
- package/gs/strconv/quote.gs.ts +335 -108
- package/gs/strconv/quote.test.ts +111 -0
- package/gs/strings/reader.test.ts +10 -10
- package/gs/strings/reader.ts +9 -9
- package/gs/strings/strings.test.ts +18 -5
- package/gs/strings/strings.ts +81 -68
- package/gs/sync/atomic/doc_64.gs.ts +24 -24
- package/gs/sync/atomic/doc_64.test.ts +5 -5
- package/gs/sync/atomic/type.gs.ts +28 -28
- package/gs/sync/sync.test.ts +109 -1
- package/gs/sync/sync.ts +46 -12
- package/gs/syscall/fs.ts +8 -8
- package/gs/syscall/net.test.ts +1 -1
- package/gs/time/parse.test.ts +45 -0
- package/gs/time/time.test.ts +46 -23
- package/gs/time/time.ts +69 -66
- package/gs/unicode/gen.go +198 -0
- package/gs/unicode/tables.ts +646 -0
- package/gs/unicode/unicode.test.ts +69 -0
- package/gs/unicode/unicode.ts +396 -312
- package/package.json +2 -2
- package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
- package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
- package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
- package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
- package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
- package/gs/github.com/aperturerobotics/util/conc/meta.json +0 -9
|
@@ -330,7 +330,7 @@ interface fileServerFileSystem {
|
|
|
330
330
|
interface fileServerFile {
|
|
331
331
|
Close(): maybePromise<$.GoError>;
|
|
332
332
|
Read(p: $.Bytes): maybePromise<[number, $.GoError]>;
|
|
333
|
-
Seek(offset:
|
|
333
|
+
Seek(offset: bigint, whence: number): maybePromise<[bigint, $.GoError]>;
|
|
334
334
|
Readdir(count: number): maybePromise<[$.Slice<fs.FileInfo> | null, $.GoError]>;
|
|
335
335
|
Stat(): maybePromise<[fs.FileInfo | null, $.GoError]>;
|
|
336
336
|
}
|
|
@@ -338,7 +338,7 @@ export declare function FS(fsys: fs.FS): FileSystem;
|
|
|
338
338
|
export declare function FileServer(root: fileServerFileSystem | null): Handler;
|
|
339
339
|
export declare function FileServerFS(fsys: fs.FS): Handler;
|
|
340
340
|
export declare function ServeFile(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null, _name: string): void;
|
|
341
|
-
export declare function ServeFileFS(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null,
|
|
341
|
+
export declare function ServeFileFS(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null, fsys: fs.FS, name: string): Promise<void>;
|
|
342
342
|
export interface Handler {
|
|
343
343
|
ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void>;
|
|
344
344
|
}
|
|
@@ -432,7 +432,7 @@ export declare function RedirectHandler(url: string, code: number): Handler;
|
|
|
432
432
|
export declare function TimeoutHandler(handler: Handler | null, _dt: number, msg: string): Handler;
|
|
433
433
|
export declare function Error(w: ResponseWriter | null, error: string, code: number): void;
|
|
434
434
|
export declare function NotFound(w: ResponseWriter | null, _r: Request | $.VarRef<Request> | null): void;
|
|
435
|
-
export declare function Redirect(w: ResponseWriter | null,
|
|
435
|
+
export declare function Redirect(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null, url: string, code: number): Promise<void>;
|
|
436
436
|
export declare function ParseTime(text: string): [time.Time, $.GoError];
|
|
437
437
|
export declare function DetectContentType(data: $.Slice<number>): string;
|
|
438
438
|
export declare function ParseHTTPVersion(vers: string): [number, number, boolean];
|
|
@@ -444,9 +444,9 @@ export declare function Get(_url: string): Promise<[Response | null, $.GoError]>
|
|
|
444
444
|
export declare function Head(_url: string): Promise<[Response | null, $.GoError]>;
|
|
445
445
|
export declare function Post(_url: string, contentType: string, body: io.Reader | null): Promise<[Response | null, $.GoError]>;
|
|
446
446
|
export declare function PostForm(_url: string, data: any): Promise<[Response | null, $.GoError]>;
|
|
447
|
-
export declare function ProxyFromEnvironment(_req: Request | $.VarRef<Request> | null): [any, $.GoError];
|
|
448
447
|
export declare function ProxyURL(fixedURL: any): (req: Request | $.VarRef<Request> | null) => [any, $.GoError];
|
|
449
|
-
export declare function
|
|
450
|
-
export declare function
|
|
448
|
+
export declare function ProxyFromEnvironment(_req: Request | $.VarRef<Request> | null): [any, $.GoError];
|
|
449
|
+
export declare function ReadRequest(reader: any): Promise<[Request | null, $.GoError]>;
|
|
450
|
+
export declare function ReadResponse(reader: any, req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]>;
|
|
451
451
|
export declare function ServeContent(w: ResponseWriter | null, req: Request | $.VarRef<Request> | null, _name: string, _modtime: time.Time, content: io.Reader | null): Promise<void>;
|
|
452
452
|
export {};
|
|
@@ -336,9 +336,10 @@ function parseRequestURL(rawURL) {
|
|
|
336
336
|
return [null, errors.New(`parse "${rawURL}": invalid URL escape`)];
|
|
337
337
|
}
|
|
338
338
|
const parsed = new URL(rawURL, 'http://goscript.invalid');
|
|
339
|
+
const path = decodeURIComponent(parsed.pathname);
|
|
339
340
|
const hasHost = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(rawURL);
|
|
340
341
|
return [
|
|
341
|
-
new RequestURL(
|
|
342
|
+
new RequestURL(path, parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search, hasHost ? parsed.protocol.replace(/:$/, '') : '', hasHost ? parsed.host : ''),
|
|
342
343
|
null,
|
|
343
344
|
];
|
|
344
345
|
}
|
|
@@ -1248,7 +1249,7 @@ function httpFileFromFSFile(file) {
|
|
|
1248
1249
|
Close: () => file.Close(),
|
|
1249
1250
|
Stat: () => file.Stat(),
|
|
1250
1251
|
Seek: seek == null ?
|
|
1251
|
-
() => [
|
|
1252
|
+
() => [0n, errors.New('net/http: file does not support seek')]
|
|
1252
1253
|
: seek.bind(file),
|
|
1253
1254
|
Readdir: readdir == null ? () => [null, io.EOF] : readdir.bind(file),
|
|
1254
1255
|
};
|
|
@@ -1279,23 +1280,7 @@ export function FileServer(root) {
|
|
|
1279
1280
|
NotFound(w, req);
|
|
1280
1281
|
return;
|
|
1281
1282
|
}
|
|
1282
|
-
|
|
1283
|
-
if (Header_Get(header, 'Content-Type') === '') {
|
|
1284
|
-
const contentType = mime.TypeByExtension(path.Ext(info?.Name?.() || req.URL?.Path || ''));
|
|
1285
|
-
if (contentType !== '') {
|
|
1286
|
-
Header_Set(header, 'Content-Type', contentType);
|
|
1287
|
-
}
|
|
1288
|
-
}
|
|
1289
|
-
if (info?.Size != null) {
|
|
1290
|
-
Header_Set(header, 'Content-Length', String(info.Size()));
|
|
1291
|
-
}
|
|
1292
|
-
await w.WriteHeader(StatusOK);
|
|
1293
|
-
if (req.Method !== MethodHead) {
|
|
1294
|
-
const [, copyErr] = await io.Copy(w, file);
|
|
1295
|
-
if (copyErr != null) {
|
|
1296
|
-
return;
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1283
|
+
await serveContent(w, req, info?.Name?.() || req.URL?.Path || '', file, typeof info?.Size === 'function' ? Number(info.Size()) : null);
|
|
1299
1284
|
}
|
|
1300
1285
|
finally {
|
|
1301
1286
|
await file.Close();
|
|
@@ -1317,8 +1302,35 @@ export function ServeFile(w, r, _name) {
|
|
|
1317
1302
|
}
|
|
1318
1303
|
NotFound(w, req);
|
|
1319
1304
|
}
|
|
1320
|
-
export function ServeFileFS(w, r,
|
|
1321
|
-
|
|
1305
|
+
export async function ServeFileFS(w, r, fsys, name) {
|
|
1306
|
+
const req = $.pointerValue(r);
|
|
1307
|
+
if (w == null || req == null) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (req.Method !== MethodGet && req.Method !== MethodHead) {
|
|
1311
|
+
Error(w, 'method not allowed', StatusMethodNotAllowed);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
const [file, err] = (await FS(fsys).Open(name)) ?? [null, fs.ErrInvalid];
|
|
1315
|
+
if (err != null || file == null) {
|
|
1316
|
+
NotFound(w, req);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
try {
|
|
1320
|
+
const [info, statErr] = await file.Stat();
|
|
1321
|
+
if (statErr != null) {
|
|
1322
|
+
Error(w, statErr.Error(), StatusInternalServerError);
|
|
1323
|
+
return;
|
|
1324
|
+
}
|
|
1325
|
+
if (info?.IsDir?.() === true) {
|
|
1326
|
+
NotFound(w, req);
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
await serveContent(w, req, info?.Name?.() || name, file, typeof info?.Size === 'function' ? Number(info.Size()) : null);
|
|
1330
|
+
}
|
|
1331
|
+
finally {
|
|
1332
|
+
await file.Close();
|
|
1333
|
+
}
|
|
1322
1334
|
}
|
|
1323
1335
|
function cleanFileServerPath(name) {
|
|
1324
1336
|
const parts = [];
|
|
@@ -1666,20 +1678,53 @@ export function HandleFunc(pattern, handler) {
|
|
|
1666
1678
|
DefaultServeMux.HandleFunc(pattern, handler);
|
|
1667
1679
|
}
|
|
1668
1680
|
export function StripPrefix(prefix, handler) {
|
|
1681
|
+
if (prefix === '') {
|
|
1682
|
+
return handler ?? NotFoundHandler();
|
|
1683
|
+
}
|
|
1669
1684
|
return {
|
|
1670
1685
|
ServeHTTP(w, r) {
|
|
1671
1686
|
const req = $.pointerValue(r);
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1687
|
+
const urlPath = req?.URL?.Path;
|
|
1688
|
+
const rawPath = req?.URL?.RawPath ?? '';
|
|
1689
|
+
const strippedPath = typeof urlPath === 'string' && urlPath.startsWith(prefix) ?
|
|
1690
|
+
urlPath.slice(prefix.length)
|
|
1691
|
+
: urlPath;
|
|
1692
|
+
const strippedRawPath = rawPath !== '' && rawPath.startsWith(prefix) ?
|
|
1693
|
+
rawPath.slice(prefix.length)
|
|
1694
|
+
: rawPath;
|
|
1695
|
+
if (req != null &&
|
|
1696
|
+
req.URL != null &&
|
|
1697
|
+
typeof urlPath === 'string' &&
|
|
1698
|
+
strippedPath.length < urlPath.length &&
|
|
1699
|
+
(rawPath === '' || strippedRawPath.length < rawPath.length)) {
|
|
1700
|
+
const reqCopy = req.Clone(req.Context());
|
|
1701
|
+
reqCopy.URL = {
|
|
1702
|
+
...reqCopy.URL,
|
|
1703
|
+
Path: strippedPath,
|
|
1704
|
+
RawPath: strippedRawPath,
|
|
1705
|
+
};
|
|
1706
|
+
return handler?.ServeHTTP(w, reqCopy);
|
|
1676
1707
|
}
|
|
1677
|
-
|
|
1708
|
+
NotFound(w, req);
|
|
1678
1709
|
},
|
|
1679
1710
|
};
|
|
1680
1711
|
}
|
|
1681
1712
|
export function AllowQuerySemicolons(handler) {
|
|
1682
|
-
|
|
1713
|
+
const target = handler ?? NotFoundHandler();
|
|
1714
|
+
return {
|
|
1715
|
+
ServeHTTP(w, r) {
|
|
1716
|
+
const req = $.pointerValue(r);
|
|
1717
|
+
if (req?.URL?.RawQuery?.includes(';') === true) {
|
|
1718
|
+
const reqCopy = req.Clone(req.Context());
|
|
1719
|
+
reqCopy.URL = {
|
|
1720
|
+
...reqCopy.URL,
|
|
1721
|
+
RawQuery: req.URL.RawQuery.replaceAll(';', '&'),
|
|
1722
|
+
};
|
|
1723
|
+
return target.ServeHTTP(w, reqCopy);
|
|
1724
|
+
}
|
|
1725
|
+
return target.ServeHTTP(w, r);
|
|
1726
|
+
},
|
|
1727
|
+
};
|
|
1683
1728
|
}
|
|
1684
1729
|
export function MaxBytesHandler(handler, n) {
|
|
1685
1730
|
return {
|
|
@@ -1723,15 +1768,46 @@ export function Error(w, error, code) {
|
|
|
1723
1768
|
export function NotFound(w, _r) {
|
|
1724
1769
|
Error(w, '404 page not found', StatusNotFound);
|
|
1725
1770
|
}
|
|
1726
|
-
export async function Redirect(w,
|
|
1771
|
+
export async function Redirect(w, r, url, code) {
|
|
1727
1772
|
if (w == null) {
|
|
1728
1773
|
return;
|
|
1729
1774
|
}
|
|
1775
|
+
const req = $.pointerValue(r);
|
|
1776
|
+
url = redirectLocation(req, url);
|
|
1730
1777
|
const header = await w.Header();
|
|
1778
|
+
const hadContentType = Header_Get(header, 'Content-Type') !== '';
|
|
1731
1779
|
if (header != null) {
|
|
1732
1780
|
Header_Set(header, 'Location', url);
|
|
1781
|
+
if (!hadContentType && req?.Method === MethodGet) {
|
|
1782
|
+
Header_Set(header, 'Content-Type', 'text/html; charset=utf-8');
|
|
1783
|
+
}
|
|
1733
1784
|
}
|
|
1734
1785
|
await w.WriteHeader(code);
|
|
1786
|
+
if (!hadContentType && req?.Method === MethodGet) {
|
|
1787
|
+
await w.Write($.stringToBytes(`<a href="${url}">${StatusText(code)}</a>.\n\n`));
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
function redirectLocation(req, url) {
|
|
1791
|
+
if (req?.URL == null ||
|
|
1792
|
+
url === '' ||
|
|
1793
|
+
url.startsWith('/') ||
|
|
1794
|
+
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(url)) {
|
|
1795
|
+
return url;
|
|
1796
|
+
}
|
|
1797
|
+
const [dir] = path.Split(req.URL.Path || '/');
|
|
1798
|
+
let pathPart = dir + url;
|
|
1799
|
+
let query = '';
|
|
1800
|
+
const queryIndex = pathPart.indexOf('?');
|
|
1801
|
+
if (queryIndex >= 0) {
|
|
1802
|
+
query = pathPart.slice(queryIndex);
|
|
1803
|
+
pathPart = pathPart.slice(0, queryIndex);
|
|
1804
|
+
}
|
|
1805
|
+
const trailingSlash = pathPart.endsWith('/');
|
|
1806
|
+
pathPart = path.Clean(pathPart);
|
|
1807
|
+
if (trailingSlash && !pathPart.endsWith('/')) {
|
|
1808
|
+
pathPart += '/';
|
|
1809
|
+
}
|
|
1810
|
+
return pathPart + query;
|
|
1735
1811
|
}
|
|
1736
1812
|
export function ParseTime(text) {
|
|
1737
1813
|
const date = new globalThis.Date(text);
|
|
@@ -1741,7 +1817,7 @@ export function ParseTime(text) {
|
|
|
1741
1817
|
$.newError(`parsing time "${text}" as HTTP-date: cannot parse`),
|
|
1742
1818
|
];
|
|
1743
1819
|
}
|
|
1744
|
-
return [time.UnixMilli(date.getTime()), null];
|
|
1820
|
+
return [time.UnixMilli(BigInt(date.getTime())), null];
|
|
1745
1821
|
}
|
|
1746
1822
|
export function DetectContentType(data) {
|
|
1747
1823
|
const bytes = Uint8Array.from(data ?? []).subarray(0, 512);
|
|
@@ -2077,7 +2153,7 @@ export function ParseSetCookie(line) {
|
|
|
2077
2153
|
if (Number.isNaN(parsed.getTime())) {
|
|
2078
2154
|
break;
|
|
2079
2155
|
}
|
|
2080
|
-
cookie.Expires = time.UnixMilli(parsed.getTime());
|
|
2156
|
+
cookie.Expires = time.UnixMilli(BigInt(parsed.getTime()));
|
|
2081
2157
|
continue;
|
|
2082
2158
|
}
|
|
2083
2159
|
case 'path':
|
|
@@ -2150,33 +2226,421 @@ export async function Post(_url, contentType, body) {
|
|
|
2150
2226
|
export async function PostForm(_url, data) {
|
|
2151
2227
|
return await DefaultClient.PostForm(_url, data);
|
|
2152
2228
|
}
|
|
2229
|
+
export function ProxyURL(fixedURL) {
|
|
2230
|
+
return () => [fixedURL, null];
|
|
2231
|
+
}
|
|
2153
2232
|
export function ProxyFromEnvironment(_req) {
|
|
2154
2233
|
return [null, null];
|
|
2155
2234
|
}
|
|
2156
|
-
export function
|
|
2157
|
-
|
|
2235
|
+
export async function ReadRequest(reader) {
|
|
2236
|
+
const [wire, readErr] = await readHTTPWire(reader);
|
|
2237
|
+
if (readErr != null) {
|
|
2238
|
+
return [null, readErr];
|
|
2239
|
+
}
|
|
2240
|
+
const parsed = parseHTTPWire(wire);
|
|
2241
|
+
if (parsed.error != null) {
|
|
2242
|
+
return [null, parsed.error];
|
|
2243
|
+
}
|
|
2244
|
+
const [method, requestURI, proto] = splitRequestLine(parsed.startLine);
|
|
2245
|
+
if (method === '' || requestURI === '' || proto === '') {
|
|
2246
|
+
return [null, badHTTPMessageError('malformed HTTP request')];
|
|
2247
|
+
}
|
|
2248
|
+
if (!isToken(method)) {
|
|
2249
|
+
return [null, badHTTPMessageError(`invalid method ${method}`)];
|
|
2250
|
+
}
|
|
2251
|
+
const [protoMajor, protoMinor, protoOK] = ParseHTTPVersion(proto);
|
|
2252
|
+
if (!protoOK) {
|
|
2253
|
+
return [null, badHTTPMessageError(`malformed HTTP version ${proto}`)];
|
|
2254
|
+
}
|
|
2255
|
+
const [url, urlErr] = parseRequestURL(requestURI);
|
|
2256
|
+
if (urlErr != null || url == null) {
|
|
2257
|
+
return [null, urlErr];
|
|
2258
|
+
}
|
|
2259
|
+
const host = Header_Get(parsed.header, 'Host');
|
|
2260
|
+
Header_Del(parsed.header, 'Host');
|
|
2261
|
+
const bodyInfo = parseHTTPBody(parsed.header, parsed.body);
|
|
2262
|
+
if (bodyInfo.error != null) {
|
|
2263
|
+
return [null, bodyInfo.error];
|
|
2264
|
+
}
|
|
2265
|
+
return [
|
|
2266
|
+
new Request({
|
|
2267
|
+
Method: method,
|
|
2268
|
+
URL: url,
|
|
2269
|
+
Proto: proto,
|
|
2270
|
+
ProtoMajor: protoMajor,
|
|
2271
|
+
ProtoMinor: protoMinor,
|
|
2272
|
+
Body: bodyInfo.body,
|
|
2273
|
+
Header: parsed.header,
|
|
2274
|
+
ContentLength: bodyInfo.contentLength,
|
|
2275
|
+
TransferEncoding: bodyInfo.transferEncoding,
|
|
2276
|
+
Close: shouldClose(protoMajor, protoMinor, parsed.header),
|
|
2277
|
+
Host: host,
|
|
2278
|
+
RequestURI: requestURI,
|
|
2279
|
+
}),
|
|
2280
|
+
null,
|
|
2281
|
+
];
|
|
2158
2282
|
}
|
|
2159
|
-
export function
|
|
2160
|
-
|
|
2283
|
+
export async function ReadResponse(reader, req) {
|
|
2284
|
+
const [wire, readErr] = await readHTTPWire(reader);
|
|
2285
|
+
if (readErr != null) {
|
|
2286
|
+
return [null, readErr];
|
|
2287
|
+
}
|
|
2288
|
+
const parsed = parseHTTPWire(wire);
|
|
2289
|
+
if (parsed.error != null) {
|
|
2290
|
+
return [null, parsed.error];
|
|
2291
|
+
}
|
|
2292
|
+
const match = /^(HTTP\/\d+\.\d+) ([0-9]{3})(?: (.*))?$/.exec(parsed.startLine);
|
|
2293
|
+
if (match == null) {
|
|
2294
|
+
return [null, badHTTPMessageError('malformed HTTP response')];
|
|
2295
|
+
}
|
|
2296
|
+
const [, proto, statusCodeText, statusText = ''] = match;
|
|
2297
|
+
const [protoMajor, protoMinor, protoOK] = ParseHTTPVersion(proto);
|
|
2298
|
+
if (!protoOK) {
|
|
2299
|
+
return [null, badHTTPMessageError(`malformed HTTP version ${proto}`)];
|
|
2300
|
+
}
|
|
2301
|
+
const statusCode = Number(statusCodeText);
|
|
2302
|
+
const noBody = statusCode === StatusNoContent || statusCode === StatusNotModified;
|
|
2303
|
+
const bodyInfo = noBody ?
|
|
2304
|
+
{
|
|
2305
|
+
body: NoBody,
|
|
2306
|
+
contentLength: 0,
|
|
2307
|
+
transferEncoding: null,
|
|
2308
|
+
error: null,
|
|
2309
|
+
}
|
|
2310
|
+
: parseHTTPBody(parsed.header, parsed.body);
|
|
2311
|
+
if (bodyInfo.error != null) {
|
|
2312
|
+
return [null, bodyInfo.error];
|
|
2313
|
+
}
|
|
2314
|
+
return [
|
|
2315
|
+
new Response({
|
|
2316
|
+
Status: `${statusCodeText}${statusText === '' ? '' : ` ${statusText}`}`,
|
|
2317
|
+
StatusCode: statusCode,
|
|
2318
|
+
Proto: proto,
|
|
2319
|
+
ProtoMajor: protoMajor,
|
|
2320
|
+
ProtoMinor: protoMinor,
|
|
2321
|
+
Body: bodyInfo.body,
|
|
2322
|
+
Header: parsed.header,
|
|
2323
|
+
ContentLength: bodyInfo.contentLength,
|
|
2324
|
+
TransferEncoding: bodyInfo.transferEncoding,
|
|
2325
|
+
Close: shouldClose(protoMajor, protoMinor, parsed.header),
|
|
2326
|
+
Request: req,
|
|
2327
|
+
}),
|
|
2328
|
+
null,
|
|
2329
|
+
];
|
|
2161
2330
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2331
|
+
async function readHTTPWire(reader) {
|
|
2332
|
+
const r = $.pointerValueOrNil(reader);
|
|
2333
|
+
if (r == null || typeof r.Read !== 'function') {
|
|
2334
|
+
return ['', errors.New('malformed HTTP message')];
|
|
2335
|
+
}
|
|
2336
|
+
const [data, err] = await io.ReadAll(r);
|
|
2337
|
+
if (err != null) {
|
|
2338
|
+
return ['', err];
|
|
2339
|
+
}
|
|
2340
|
+
return [$.bytesToString(data), null];
|
|
2341
|
+
}
|
|
2342
|
+
function splitRequestLine(line) {
|
|
2343
|
+
const first = line.indexOf(' ');
|
|
2344
|
+
const last = line.lastIndexOf(' ');
|
|
2345
|
+
if (first <= 0 || last <= first) {
|
|
2346
|
+
return ['', '', ''];
|
|
2347
|
+
}
|
|
2348
|
+
return [
|
|
2349
|
+
line.slice(0, first),
|
|
2350
|
+
line.slice(first + 1, last),
|
|
2351
|
+
line.slice(last + 1),
|
|
2352
|
+
];
|
|
2353
|
+
}
|
|
2354
|
+
function parseHTTPWire(wire) {
|
|
2355
|
+
const headerEnd = wire.indexOf('\r\n\r\n');
|
|
2356
|
+
const separatorLength = headerEnd >= 0 ? 4 : 2;
|
|
2357
|
+
const fallbackHeaderEnd = headerEnd >= 0 ? headerEnd : wire.indexOf('\n\n');
|
|
2358
|
+
if (fallbackHeaderEnd < 0) {
|
|
2359
|
+
return {
|
|
2360
|
+
startLine: '',
|
|
2361
|
+
header: new Header(),
|
|
2362
|
+
body: '',
|
|
2363
|
+
error: badHTTPMessageError('malformed HTTP message'),
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
const head = wire.slice(0, fallbackHeaderEnd).replace(/\r\n/g, '\n');
|
|
2367
|
+
const lines = head.split('\n');
|
|
2368
|
+
const startLine = lines.shift() ?? '';
|
|
2369
|
+
const header = new Header();
|
|
2370
|
+
let lastKey = '';
|
|
2371
|
+
for (const rawLine of lines) {
|
|
2372
|
+
if (rawLine === '') {
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
if ((rawLine[0] === ' ' || rawLine[0] === '\t') && lastKey !== '') {
|
|
2376
|
+
const values = Array.from(header.get(lastKey) ?? []);
|
|
2377
|
+
values[values.length - 1] =
|
|
2378
|
+
`${values[values.length - 1]} ${rawLine.trim()}`;
|
|
2379
|
+
header.set(lastKey, $.arrayToSlice(values));
|
|
2380
|
+
continue;
|
|
2381
|
+
}
|
|
2382
|
+
const colon = rawLine.indexOf(':');
|
|
2383
|
+
if (colon <= 0) {
|
|
2384
|
+
return {
|
|
2385
|
+
startLine: '',
|
|
2386
|
+
header: new Header(),
|
|
2387
|
+
body: '',
|
|
2388
|
+
error: badHTTPMessageError('malformed MIME header line'),
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
lastKey = canonicalMIMEHeaderKey(rawLine.slice(0, colon).trim());
|
|
2392
|
+
Header_Add(header, lastKey, rawLine.slice(colon + 1).trim());
|
|
2393
|
+
}
|
|
2394
|
+
return {
|
|
2395
|
+
startLine,
|
|
2396
|
+
header,
|
|
2397
|
+
body: wire.slice(fallbackHeaderEnd + separatorLength),
|
|
2398
|
+
error: null,
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
function parseHTTPBody(header, rawBody) {
|
|
2402
|
+
const transferEncoding = Header_Get(header, 'Transfer-Encoding');
|
|
2403
|
+
if (transferEncoding.toLowerCase() === 'chunked') {
|
|
2404
|
+
const chunked = decodeChunkedBody(rawBody);
|
|
2405
|
+
if (chunked.error != null) {
|
|
2406
|
+
return {
|
|
2407
|
+
body: NoBody,
|
|
2408
|
+
contentLength: -1,
|
|
2409
|
+
transferEncoding: $.arrayToSlice(['chunked']),
|
|
2410
|
+
error: chunked.error,
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
return {
|
|
2414
|
+
body: bodyFromString(chunked.body),
|
|
2415
|
+
contentLength: -1,
|
|
2416
|
+
transferEncoding: $.arrayToSlice(['chunked']),
|
|
2417
|
+
error: null,
|
|
2418
|
+
};
|
|
2419
|
+
}
|
|
2420
|
+
const contentLength = Header_Get(header, 'Content-Length');
|
|
2421
|
+
if (contentLength !== '') {
|
|
2422
|
+
const parsedLength = Number.parseInt(contentLength, 10);
|
|
2423
|
+
if (!/^[0-9]+$/.test(contentLength) ||
|
|
2424
|
+
!Number.isSafeInteger(parsedLength)) {
|
|
2425
|
+
return {
|
|
2426
|
+
body: NoBody,
|
|
2427
|
+
contentLength: 0,
|
|
2428
|
+
transferEncoding: null,
|
|
2429
|
+
error: badHTTPMessageError(`bad Content-Length ${contentLength}`),
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
return {
|
|
2433
|
+
body: bodyFromString(rawBody.slice(0, parsedLength)),
|
|
2434
|
+
contentLength: parsedLength,
|
|
2435
|
+
transferEncoding: null,
|
|
2436
|
+
error: null,
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
if (rawBody === '') {
|
|
2440
|
+
return {
|
|
2441
|
+
body: NoBody,
|
|
2442
|
+
contentLength: 0,
|
|
2443
|
+
transferEncoding: null,
|
|
2444
|
+
error: null,
|
|
2445
|
+
};
|
|
2446
|
+
}
|
|
2447
|
+
return {
|
|
2448
|
+
body: bodyFromString(rawBody),
|
|
2449
|
+
contentLength: -1,
|
|
2450
|
+
transferEncoding: null,
|
|
2451
|
+
error: null,
|
|
2452
|
+
};
|
|
2453
|
+
}
|
|
2454
|
+
function decodeChunkedBody(rawBody) {
|
|
2455
|
+
let offset = 0;
|
|
2456
|
+
let body = '';
|
|
2457
|
+
while (true) {
|
|
2458
|
+
const lineEnd = rawBody.indexOf('\r\n', offset);
|
|
2459
|
+
if (lineEnd < 0) {
|
|
2460
|
+
return { body: '', error: io.ErrUnexpectedEOF };
|
|
2461
|
+
}
|
|
2462
|
+
const sizeText = rawBody.slice(offset, lineEnd).split(';', 1)[0].trim();
|
|
2463
|
+
const size = Number.parseInt(sizeText, 16);
|
|
2464
|
+
if (!/^[0-9A-Fa-f]+$/.test(sizeText) || !Number.isSafeInteger(size)) {
|
|
2465
|
+
return { body: '', error: badHTTPMessageError('invalid chunk length') };
|
|
2466
|
+
}
|
|
2467
|
+
offset = lineEnd + 2;
|
|
2468
|
+
if (size === 0) {
|
|
2469
|
+
return { body, error: null };
|
|
2470
|
+
}
|
|
2471
|
+
if (offset + size + 2 > rawBody.length) {
|
|
2472
|
+
return { body: '', error: io.ErrUnexpectedEOF };
|
|
2473
|
+
}
|
|
2474
|
+
body += rawBody.slice(offset, offset + size);
|
|
2475
|
+
offset += size;
|
|
2476
|
+
if (rawBody.slice(offset, offset + 2) !== '\r\n') {
|
|
2477
|
+
return { body: '', error: badHTTPMessageError('malformed chunk') };
|
|
2478
|
+
}
|
|
2479
|
+
offset += 2;
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
function bodyFromString(body) {
|
|
2483
|
+
return body === '' ? NoBody : new responseBody($.stringToBytes(body));
|
|
2484
|
+
}
|
|
2485
|
+
function shouldClose(protoMajor, protoMinor, header) {
|
|
2486
|
+
const connection = Header_Get(header, 'Connection').toLowerCase();
|
|
2487
|
+
if (connection
|
|
2488
|
+
.split(',')
|
|
2489
|
+
.map((part) => part.trim())
|
|
2490
|
+
.includes('close')) {
|
|
2491
|
+
return true;
|
|
2492
|
+
}
|
|
2493
|
+
if (protoMajor < 1 || (protoMajor === 1 && protoMinor === 0)) {
|
|
2494
|
+
return !connection
|
|
2495
|
+
.split(',')
|
|
2496
|
+
.map((part) => part.trim())
|
|
2497
|
+
.includes('keep-alive');
|
|
2498
|
+
}
|
|
2499
|
+
return false;
|
|
2500
|
+
}
|
|
2501
|
+
function badHTTPMessageError(message) {
|
|
2502
|
+
return errors.New(message);
|
|
2164
2503
|
}
|
|
2165
2504
|
export async function ServeContent(w, req, _name, _modtime, content) {
|
|
2505
|
+
await serveContent(w, $.pointerValueOrNil(req), _name, content, null);
|
|
2506
|
+
}
|
|
2507
|
+
async function serveContent(w, req, name, content, knownSize) {
|
|
2508
|
+
// Browser media seeks depend on FileServer and ServeContent sharing byte-range semantics.
|
|
2166
2509
|
if (content == null) {
|
|
2167
2510
|
NotFound(w, req);
|
|
2168
2511
|
return;
|
|
2169
2512
|
}
|
|
2170
|
-
|
|
2171
|
-
if (err != null) {
|
|
2172
|
-
Error(w, err.Error(), StatusInternalServerError);
|
|
2513
|
+
if (w == null) {
|
|
2173
2514
|
return;
|
|
2174
2515
|
}
|
|
2175
|
-
w
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2516
|
+
const header = await w.Header();
|
|
2517
|
+
if (Header_Get(header, 'Content-Type') === '') {
|
|
2518
|
+
const contentType = mime.TypeByExtension(path.Ext(name));
|
|
2519
|
+
if (contentType !== '') {
|
|
2520
|
+
Header_Set(header, 'Content-Type', contentType);
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
const rangeHeader = req?.Header == null ? '' : Header_Get(req.Header, 'Range');
|
|
2524
|
+
if (rangeHeader === '' && knownSize != null) {
|
|
2525
|
+
Header_Set(header, 'Content-Length', String(knownSize));
|
|
2526
|
+
await w.WriteHeader(StatusOK);
|
|
2527
|
+
if (req?.Method !== MethodHead) {
|
|
2528
|
+
await io.Copy(w, content);
|
|
2529
|
+
}
|
|
2530
|
+
return;
|
|
2531
|
+
}
|
|
2532
|
+
const seeker = content;
|
|
2533
|
+
if (typeof seeker.Seek !== 'function') {
|
|
2534
|
+
const [data, err] = await io.ReadAll(content);
|
|
2535
|
+
if (err != null) {
|
|
2536
|
+
Error(w, err.Error(), StatusInternalServerError);
|
|
2537
|
+
return;
|
|
2538
|
+
}
|
|
2539
|
+
const body = data ?? new Uint8Array(0);
|
|
2540
|
+
Header_Set(header, 'Content-Length', String(body.length));
|
|
2541
|
+
await w.WriteHeader(StatusOK);
|
|
2542
|
+
if (req?.Method !== MethodHead) {
|
|
2543
|
+
await w.Write(body);
|
|
2544
|
+
}
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
let size = knownSize;
|
|
2548
|
+
if (size == null) {
|
|
2549
|
+
const [end, err] = await seeker.Seek(0n, io.SeekEnd);
|
|
2550
|
+
if (err != null) {
|
|
2551
|
+
Error(w, err.Error(), StatusInternalServerError);
|
|
2552
|
+
return;
|
|
2553
|
+
}
|
|
2554
|
+
size = Number(end);
|
|
2555
|
+
}
|
|
2556
|
+
const [, seekErr] = await seeker.Seek(0n, io.SeekStart);
|
|
2557
|
+
if (seekErr != null) {
|
|
2558
|
+
Error(w, seekErr.Error(), StatusInternalServerError);
|
|
2559
|
+
return;
|
|
2560
|
+
}
|
|
2561
|
+
Header_Set(header, 'Accept-Ranges', 'bytes');
|
|
2562
|
+
const parsedRange = rangeHeader === '' ? null : parseHTTPRange(rangeHeader, size);
|
|
2563
|
+
if (parsedRange?.error === true) {
|
|
2564
|
+
Header_Set(header, 'Content-Range', `bytes */${size}`);
|
|
2565
|
+
Header_Set(header, 'Content-Length', '0');
|
|
2566
|
+
await w.WriteHeader(StatusRequestedRangeNotSatisfiable);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
let status = StatusOK;
|
|
2570
|
+
let start = 0;
|
|
2571
|
+
let length = size;
|
|
2572
|
+
if (parsedRange?.range != null) {
|
|
2573
|
+
status = StatusPartialContent;
|
|
2574
|
+
start = parsedRange.range.start;
|
|
2575
|
+
length = parsedRange.range.length;
|
|
2576
|
+
Header_Set(header, 'Content-Range', `bytes ${start}-${start + length - 1}/${size}`);
|
|
2577
|
+
}
|
|
2578
|
+
Header_Set(header, 'Content-Length', String(length));
|
|
2579
|
+
const [, rangeSeekErr] = await seeker.Seek(BigInt(start), io.SeekStart);
|
|
2580
|
+
if (rangeSeekErr != null) {
|
|
2581
|
+
Error(w, rangeSeekErr.Error(), StatusInternalServerError);
|
|
2582
|
+
return;
|
|
2583
|
+
}
|
|
2584
|
+
await w.WriteHeader(status);
|
|
2585
|
+
if (req?.Method === MethodHead) {
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
if (length === 0) {
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
await io.CopyN(w, seeker, BigInt(length));
|
|
2592
|
+
}
|
|
2593
|
+
function parseHTTPRange(header, size) {
|
|
2594
|
+
if (!header.startsWith('bytes=') || header.includes(',')) {
|
|
2595
|
+
return { range: null, error: true };
|
|
2596
|
+
}
|
|
2597
|
+
const spec = header.slice('bytes='.length).trim();
|
|
2598
|
+
const dash = spec.indexOf('-');
|
|
2599
|
+
if (dash < 0) {
|
|
2600
|
+
return { range: null, error: true };
|
|
2601
|
+
}
|
|
2602
|
+
const startText = spec.slice(0, dash).trim();
|
|
2603
|
+
const endText = spec.slice(dash + 1).trim();
|
|
2604
|
+
if (startText === '' && endText === '') {
|
|
2605
|
+
return { range: null, error: true };
|
|
2606
|
+
}
|
|
2607
|
+
if (size < 0) {
|
|
2608
|
+
return { range: null, error: true };
|
|
2609
|
+
}
|
|
2610
|
+
if (startText === '') {
|
|
2611
|
+
const suffixLength = parseHTTPRangeNumber(endText);
|
|
2612
|
+
if (suffixLength == null || suffixLength <= 0) {
|
|
2613
|
+
return { range: null, error: true };
|
|
2614
|
+
}
|
|
2615
|
+
if (size === 0) {
|
|
2616
|
+
return { range: null, error: true };
|
|
2617
|
+
}
|
|
2618
|
+
const length = Math.min(suffixLength, size);
|
|
2619
|
+
return { range: { start: size - length, length }, error: false };
|
|
2620
|
+
}
|
|
2621
|
+
const start = parseHTTPRangeNumber(startText);
|
|
2622
|
+
if (start == null || start >= size) {
|
|
2623
|
+
return { range: null, error: true };
|
|
2624
|
+
}
|
|
2625
|
+
let end = size - 1;
|
|
2626
|
+
if (endText !== '') {
|
|
2627
|
+
const parsedEnd = parseHTTPRangeNumber(endText);
|
|
2628
|
+
if (parsedEnd == null || parsedEnd < start) {
|
|
2629
|
+
return { range: null, error: true };
|
|
2630
|
+
}
|
|
2631
|
+
end = Math.min(parsedEnd, size - 1);
|
|
2632
|
+
}
|
|
2633
|
+
return { range: { start, length: end - start + 1 }, error: false };
|
|
2634
|
+
}
|
|
2635
|
+
function parseHTTPRangeNumber(value) {
|
|
2636
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
2637
|
+
return null;
|
|
2638
|
+
}
|
|
2639
|
+
const parsed = Number(value);
|
|
2640
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
2641
|
+
return null;
|
|
2179
2642
|
}
|
|
2643
|
+
return parsed;
|
|
2180
2644
|
}
|
|
2181
2645
|
function readCloserForBody(body) {
|
|
2182
2646
|
if (body == null) {
|