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
package/gs/net/http/index.ts
CHANGED
|
@@ -420,10 +420,11 @@ function parseRequestURL(rawURL: string): [RequestURL | null, $.GoError] {
|
|
|
420
420
|
return [null, errors.New(`parse "${rawURL}": invalid URL escape`)]
|
|
421
421
|
}
|
|
422
422
|
const parsed = new URL(rawURL, 'http://goscript.invalid')
|
|
423
|
+
const path = decodeURIComponent(parsed.pathname)
|
|
423
424
|
const hasHost = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(rawURL)
|
|
424
425
|
return [
|
|
425
426
|
new RequestURL(
|
|
426
|
-
|
|
427
|
+
path,
|
|
427
428
|
parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search,
|
|
428
429
|
hasHost ? parsed.protocol.replace(/:$/, '') : '',
|
|
429
430
|
hasHost ? parsed.host : '',
|
|
@@ -1508,6 +1509,11 @@ async function readFetchBody(
|
|
|
1508
1509
|
|
|
1509
1510
|
type maybePromise<T> = T | Promise<T>
|
|
1510
1511
|
|
|
1512
|
+
type httpRange = {
|
|
1513
|
+
start: number
|
|
1514
|
+
length: number
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1511
1517
|
export interface FileSystem {
|
|
1512
1518
|
Open(name: string): maybePromise<[File | null, $.GoError]>
|
|
1513
1519
|
}
|
|
@@ -1524,7 +1530,7 @@ interface fileServerFileSystem {
|
|
|
1524
1530
|
interface fileServerFile {
|
|
1525
1531
|
Close(): maybePromise<$.GoError>
|
|
1526
1532
|
Read(p: $.Bytes): maybePromise<[number, $.GoError]>
|
|
1527
|
-
Seek(offset:
|
|
1533
|
+
Seek(offset: bigint, whence: number): maybePromise<[bigint, $.GoError]>
|
|
1528
1534
|
Readdir(count: number): maybePromise<[$.Slice<fs.FileInfo> | null, $.GoError]>
|
|
1529
1535
|
Stat(): maybePromise<[fs.FileInfo | null, $.GoError]>
|
|
1530
1536
|
}
|
|
@@ -1554,7 +1560,7 @@ function httpFileFromFSFile(file: Exclude<fs.File, null>): File {
|
|
|
1554
1560
|
Stat: () => file.Stat(),
|
|
1555
1561
|
Seek:
|
|
1556
1562
|
seek == null ?
|
|
1557
|
-
() => [
|
|
1563
|
+
() => [0n, errors.New('net/http: file does not support seek')]
|
|
1558
1564
|
: seek.bind(file),
|
|
1559
1565
|
Readdir: readdir == null ? () => [null, io.EOF] : readdir.bind(file),
|
|
1560
1566
|
}
|
|
@@ -1588,28 +1594,13 @@ export function FileServer(root: fileServerFileSystem | null): Handler {
|
|
|
1588
1594
|
NotFound(w, req)
|
|
1589
1595
|
return
|
|
1590
1596
|
}
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
if (info?.Size != null) {
|
|
1601
|
-
Header_Set(header, 'Content-Length', String(info.Size()))
|
|
1602
|
-
}
|
|
1603
|
-
await w.WriteHeader(StatusOK)
|
|
1604
|
-
if (req.Method !== MethodHead) {
|
|
1605
|
-
const [, copyErr] = await io.Copy(
|
|
1606
|
-
w as unknown as io.Writer,
|
|
1607
|
-
file as io.Reader,
|
|
1608
|
-
)
|
|
1609
|
-
if (copyErr != null) {
|
|
1610
|
-
return
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1597
|
+
await serveContent(
|
|
1598
|
+
w,
|
|
1599
|
+
req,
|
|
1600
|
+
info?.Name?.() || req.URL?.Path || '',
|
|
1601
|
+
file as io.Reader,
|
|
1602
|
+
typeof info?.Size === 'function' ? Number(info.Size()) : null,
|
|
1603
|
+
)
|
|
1613
1604
|
} finally {
|
|
1614
1605
|
await file.Close()
|
|
1615
1606
|
}
|
|
@@ -1637,13 +1628,45 @@ export function ServeFile(
|
|
|
1637
1628
|
NotFound(w, req)
|
|
1638
1629
|
}
|
|
1639
1630
|
|
|
1640
|
-
export function ServeFileFS(
|
|
1631
|
+
export async function ServeFileFS(
|
|
1641
1632
|
w: ResponseWriter | null,
|
|
1642
1633
|
r: Request | $.VarRef<Request> | null,
|
|
1643
|
-
|
|
1634
|
+
fsys: fs.FS,
|
|
1644
1635
|
name: string,
|
|
1645
|
-
): void {
|
|
1646
|
-
|
|
1636
|
+
): Promise<void> {
|
|
1637
|
+
const req = $.pointerValue<Request | null>(r)
|
|
1638
|
+
if (w == null || req == null) {
|
|
1639
|
+
return
|
|
1640
|
+
}
|
|
1641
|
+
if (req.Method !== MethodGet && req.Method !== MethodHead) {
|
|
1642
|
+
Error(w, 'method not allowed', StatusMethodNotAllowed)
|
|
1643
|
+
return
|
|
1644
|
+
}
|
|
1645
|
+
const [file, err] = (await FS(fsys).Open(name)) ?? [null, fs.ErrInvalid]
|
|
1646
|
+
if (err != null || file == null) {
|
|
1647
|
+
NotFound(w, req)
|
|
1648
|
+
return
|
|
1649
|
+
}
|
|
1650
|
+
try {
|
|
1651
|
+
const [info, statErr] = await file.Stat()
|
|
1652
|
+
if (statErr != null) {
|
|
1653
|
+
Error(w, statErr.Error(), StatusInternalServerError)
|
|
1654
|
+
return
|
|
1655
|
+
}
|
|
1656
|
+
if (info?.IsDir?.() === true) {
|
|
1657
|
+
NotFound(w, req)
|
|
1658
|
+
return
|
|
1659
|
+
}
|
|
1660
|
+
await serveContent(
|
|
1661
|
+
w,
|
|
1662
|
+
req,
|
|
1663
|
+
info?.Name?.() || name,
|
|
1664
|
+
file as io.Reader,
|
|
1665
|
+
typeof info?.Size === 'function' ? Number(info.Size()) : null,
|
|
1666
|
+
)
|
|
1667
|
+
} finally {
|
|
1668
|
+
await file.Close()
|
|
1669
|
+
}
|
|
1647
1670
|
}
|
|
1648
1671
|
|
|
1649
1672
|
function cleanFileServerPath(name: string): string {
|
|
@@ -2115,23 +2138,58 @@ export function HandleFunc(pattern: string, handler: HandlerFunc): void {
|
|
|
2115
2138
|
}
|
|
2116
2139
|
|
|
2117
2140
|
export function StripPrefix(prefix: string, handler: Handler | null): Handler {
|
|
2141
|
+
if (prefix === '') {
|
|
2142
|
+
return handler ?? NotFoundHandler()
|
|
2143
|
+
}
|
|
2118
2144
|
return {
|
|
2119
2145
|
ServeHTTP(w, r) {
|
|
2120
2146
|
const req = $.pointerValue<Request | null>(r)
|
|
2147
|
+
const urlPath = req?.URL?.Path
|
|
2148
|
+
const rawPath = req?.URL?.RawPath ?? ''
|
|
2149
|
+
const strippedPath =
|
|
2150
|
+
typeof urlPath === 'string' && urlPath.startsWith(prefix) ?
|
|
2151
|
+
urlPath.slice(prefix.length)
|
|
2152
|
+
: urlPath
|
|
2153
|
+
const strippedRawPath =
|
|
2154
|
+
rawPath !== '' && rawPath.startsWith(prefix) ?
|
|
2155
|
+
rawPath.slice(prefix.length)
|
|
2156
|
+
: rawPath
|
|
2121
2157
|
if (
|
|
2122
|
-
req
|
|
2123
|
-
|
|
2124
|
-
|
|
2158
|
+
req != null &&
|
|
2159
|
+
req.URL != null &&
|
|
2160
|
+
typeof urlPath === 'string' &&
|
|
2161
|
+
strippedPath.length < urlPath.length &&
|
|
2162
|
+
(rawPath === '' || strippedRawPath.length < rawPath.length)
|
|
2125
2163
|
) {
|
|
2126
|
-
|
|
2164
|
+
const reqCopy = req.Clone(req.Context())
|
|
2165
|
+
reqCopy.URL = {
|
|
2166
|
+
...reqCopy.URL,
|
|
2167
|
+
Path: strippedPath,
|
|
2168
|
+
RawPath: strippedRawPath,
|
|
2169
|
+
}
|
|
2170
|
+
return handler?.ServeHTTP(w, reqCopy)
|
|
2127
2171
|
}
|
|
2128
|
-
|
|
2172
|
+
NotFound(w, req)
|
|
2129
2173
|
},
|
|
2130
2174
|
}
|
|
2131
2175
|
}
|
|
2132
2176
|
|
|
2133
2177
|
export function AllowQuerySemicolons(handler: Handler | null): Handler {
|
|
2134
|
-
|
|
2178
|
+
const target = handler ?? NotFoundHandler()
|
|
2179
|
+
return {
|
|
2180
|
+
ServeHTTP(w, r) {
|
|
2181
|
+
const req = $.pointerValue<Request | null>(r)
|
|
2182
|
+
if (req?.URL?.RawQuery?.includes(';') === true) {
|
|
2183
|
+
const reqCopy = req.Clone(req.Context())
|
|
2184
|
+
reqCopy.URL = {
|
|
2185
|
+
...reqCopy.URL,
|
|
2186
|
+
RawQuery: req.URL.RawQuery.replaceAll(';', '&'),
|
|
2187
|
+
}
|
|
2188
|
+
return target.ServeHTTP(w, reqCopy)
|
|
2189
|
+
}
|
|
2190
|
+
return target.ServeHTTP(w, r)
|
|
2191
|
+
},
|
|
2192
|
+
}
|
|
2135
2193
|
}
|
|
2136
2194
|
|
|
2137
2195
|
export function MaxBytesHandler(handler: Handler | null, n: number): Handler {
|
|
@@ -2198,18 +2256,54 @@ export function NotFound(
|
|
|
2198
2256
|
|
|
2199
2257
|
export async function Redirect(
|
|
2200
2258
|
w: ResponseWriter | null,
|
|
2201
|
-
|
|
2259
|
+
r: Request | $.VarRef<Request> | null,
|
|
2202
2260
|
url: string,
|
|
2203
2261
|
code: number,
|
|
2204
2262
|
): Promise<void> {
|
|
2205
2263
|
if (w == null) {
|
|
2206
2264
|
return
|
|
2207
2265
|
}
|
|
2266
|
+
const req = $.pointerValue<Request | null>(r)
|
|
2267
|
+
url = redirectLocation(req, url)
|
|
2208
2268
|
const header = await w.Header()
|
|
2269
|
+
const hadContentType = Header_Get(header, 'Content-Type') !== ''
|
|
2209
2270
|
if (header != null) {
|
|
2210
2271
|
Header_Set(header, 'Location', url)
|
|
2272
|
+
if (!hadContentType && req?.Method === MethodGet) {
|
|
2273
|
+
Header_Set(header, 'Content-Type', 'text/html; charset=utf-8')
|
|
2274
|
+
}
|
|
2211
2275
|
}
|
|
2212
2276
|
await w.WriteHeader(code)
|
|
2277
|
+
if (!hadContentType && req?.Method === MethodGet) {
|
|
2278
|
+
await w.Write(
|
|
2279
|
+
$.stringToBytes(`<a href="${url}">${StatusText(code)}</a>.\n\n`),
|
|
2280
|
+
)
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
function redirectLocation(req: Request | null, url: string): string {
|
|
2285
|
+
if (
|
|
2286
|
+
req?.URL == null ||
|
|
2287
|
+
url === '' ||
|
|
2288
|
+
url.startsWith('/') ||
|
|
2289
|
+
/^[A-Za-z][A-Za-z0-9+.-]*:/.test(url)
|
|
2290
|
+
) {
|
|
2291
|
+
return url
|
|
2292
|
+
}
|
|
2293
|
+
const [dir] = path.Split(req.URL.Path || '/')
|
|
2294
|
+
let pathPart = dir + url
|
|
2295
|
+
let query = ''
|
|
2296
|
+
const queryIndex = pathPart.indexOf('?')
|
|
2297
|
+
if (queryIndex >= 0) {
|
|
2298
|
+
query = pathPart.slice(queryIndex)
|
|
2299
|
+
pathPart = pathPart.slice(0, queryIndex)
|
|
2300
|
+
}
|
|
2301
|
+
const trailingSlash = pathPart.endsWith('/')
|
|
2302
|
+
pathPart = path.Clean(pathPart)
|
|
2303
|
+
if (trailingSlash && !pathPart.endsWith('/')) {
|
|
2304
|
+
pathPart += '/'
|
|
2305
|
+
}
|
|
2306
|
+
return pathPart + query
|
|
2213
2307
|
}
|
|
2214
2308
|
|
|
2215
2309
|
export function ParseTime(text: string): [time.Time, $.GoError] {
|
|
@@ -2220,7 +2314,7 @@ export function ParseTime(text: string): [time.Time, $.GoError] {
|
|
|
2220
2314
|
$.newError(`parsing time "${text}" as HTTP-date: cannot parse`),
|
|
2221
2315
|
]
|
|
2222
2316
|
}
|
|
2223
|
-
return [time.UnixMilli(date.getTime()), null]
|
|
2317
|
+
return [time.UnixMilli(BigInt(date.getTime())), null]
|
|
2224
2318
|
}
|
|
2225
2319
|
|
|
2226
2320
|
export function DetectContentType(data: $.Slice<number>): string {
|
|
@@ -2604,7 +2698,7 @@ export function ParseSetCookie(line: string): [Cookie | null, $.GoError] {
|
|
|
2604
2698
|
if (Number.isNaN(parsed.getTime())) {
|
|
2605
2699
|
break
|
|
2606
2700
|
}
|
|
2607
|
-
cookie.Expires = time.UnixMilli(parsed.getTime())
|
|
2701
|
+
cookie.Expires = time.UnixMilli(BigInt(parsed.getTime()))
|
|
2608
2702
|
continue
|
|
2609
2703
|
}
|
|
2610
2704
|
case 'path':
|
|
@@ -2702,27 +2796,326 @@ export async function PostForm(
|
|
|
2702
2796
|
return await DefaultClient.PostForm(_url, data)
|
|
2703
2797
|
}
|
|
2704
2798
|
|
|
2799
|
+
export function ProxyURL(
|
|
2800
|
+
fixedURL: any,
|
|
2801
|
+
): (req: Request | $.VarRef<Request> | null) => [any, $.GoError] {
|
|
2802
|
+
return () => [fixedURL, null]
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2705
2805
|
export function ProxyFromEnvironment(
|
|
2706
2806
|
_req: Request | $.VarRef<Request> | null,
|
|
2707
2807
|
): [any, $.GoError] {
|
|
2708
2808
|
return [null, null]
|
|
2709
2809
|
}
|
|
2710
2810
|
|
|
2711
|
-
export function
|
|
2712
|
-
|
|
2713
|
-
):
|
|
2714
|
-
|
|
2811
|
+
export async function ReadRequest(
|
|
2812
|
+
reader: any,
|
|
2813
|
+
): Promise<[Request | null, $.GoError]> {
|
|
2814
|
+
const [wire, readErr] = await readHTTPWire(reader)
|
|
2815
|
+
if (readErr != null) {
|
|
2816
|
+
return [null, readErr]
|
|
2817
|
+
}
|
|
2818
|
+
const parsed = parseHTTPWire(wire)
|
|
2819
|
+
if (parsed.error != null) {
|
|
2820
|
+
return [null, parsed.error]
|
|
2821
|
+
}
|
|
2822
|
+
const [method, requestURI, proto] = splitRequestLine(parsed.startLine)
|
|
2823
|
+
if (method === '' || requestURI === '' || proto === '') {
|
|
2824
|
+
return [null, badHTTPMessageError('malformed HTTP request')]
|
|
2825
|
+
}
|
|
2826
|
+
if (!isToken(method)) {
|
|
2827
|
+
return [null, badHTTPMessageError(`invalid method ${method}`)]
|
|
2828
|
+
}
|
|
2829
|
+
const [protoMajor, protoMinor, protoOK] = ParseHTTPVersion(proto)
|
|
2830
|
+
if (!protoOK) {
|
|
2831
|
+
return [null, badHTTPMessageError(`malformed HTTP version ${proto}`)]
|
|
2832
|
+
}
|
|
2833
|
+
const [url, urlErr] = parseRequestURL(requestURI)
|
|
2834
|
+
if (urlErr != null || url == null) {
|
|
2835
|
+
return [null, urlErr]
|
|
2836
|
+
}
|
|
2837
|
+
const host = Header_Get(parsed.header, 'Host')
|
|
2838
|
+
Header_Del(parsed.header, 'Host')
|
|
2839
|
+
const bodyInfo = parseHTTPBody(parsed.header, parsed.body)
|
|
2840
|
+
if (bodyInfo.error != null) {
|
|
2841
|
+
return [null, bodyInfo.error]
|
|
2842
|
+
}
|
|
2843
|
+
return [
|
|
2844
|
+
new Request({
|
|
2845
|
+
Method: method,
|
|
2846
|
+
URL: url,
|
|
2847
|
+
Proto: proto,
|
|
2848
|
+
ProtoMajor: protoMajor,
|
|
2849
|
+
ProtoMinor: protoMinor,
|
|
2850
|
+
Body: bodyInfo.body,
|
|
2851
|
+
Header: parsed.header,
|
|
2852
|
+
ContentLength: bodyInfo.contentLength,
|
|
2853
|
+
TransferEncoding: bodyInfo.transferEncoding,
|
|
2854
|
+
Close: shouldClose(protoMajor, protoMinor, parsed.header),
|
|
2855
|
+
Host: host,
|
|
2856
|
+
RequestURI: requestURI,
|
|
2857
|
+
}),
|
|
2858
|
+
null,
|
|
2859
|
+
]
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
export async function ReadResponse(
|
|
2863
|
+
reader: any,
|
|
2864
|
+
req: Request | $.VarRef<Request> | null,
|
|
2865
|
+
): Promise<[Response | null, $.GoError]> {
|
|
2866
|
+
const [wire, readErr] = await readHTTPWire(reader)
|
|
2867
|
+
if (readErr != null) {
|
|
2868
|
+
return [null, readErr]
|
|
2869
|
+
}
|
|
2870
|
+
const parsed = parseHTTPWire(wire)
|
|
2871
|
+
if (parsed.error != null) {
|
|
2872
|
+
return [null, parsed.error]
|
|
2873
|
+
}
|
|
2874
|
+
const match = /^(HTTP\/\d+\.\d+) ([0-9]{3})(?: (.*))?$/.exec(parsed.startLine)
|
|
2875
|
+
if (match == null) {
|
|
2876
|
+
return [null, badHTTPMessageError('malformed HTTP response')]
|
|
2877
|
+
}
|
|
2878
|
+
const [, proto, statusCodeText, statusText = ''] = match
|
|
2879
|
+
const [protoMajor, protoMinor, protoOK] = ParseHTTPVersion(proto)
|
|
2880
|
+
if (!protoOK) {
|
|
2881
|
+
return [null, badHTTPMessageError(`malformed HTTP version ${proto}`)]
|
|
2882
|
+
}
|
|
2883
|
+
const statusCode = Number(statusCodeText)
|
|
2884
|
+
const noBody =
|
|
2885
|
+
statusCode === StatusNoContent || statusCode === StatusNotModified
|
|
2886
|
+
const bodyInfo =
|
|
2887
|
+
noBody ?
|
|
2888
|
+
{
|
|
2889
|
+
body: NoBody,
|
|
2890
|
+
contentLength: 0,
|
|
2891
|
+
transferEncoding: null,
|
|
2892
|
+
error: null,
|
|
2893
|
+
}
|
|
2894
|
+
: parseHTTPBody(parsed.header, parsed.body)
|
|
2895
|
+
if (bodyInfo.error != null) {
|
|
2896
|
+
return [null, bodyInfo.error]
|
|
2897
|
+
}
|
|
2898
|
+
return [
|
|
2899
|
+
new Response({
|
|
2900
|
+
Status: `${statusCodeText}${statusText === '' ? '' : ` ${statusText}`}`,
|
|
2901
|
+
StatusCode: statusCode,
|
|
2902
|
+
Proto: proto,
|
|
2903
|
+
ProtoMajor: protoMajor,
|
|
2904
|
+
ProtoMinor: protoMinor,
|
|
2905
|
+
Body: bodyInfo.body,
|
|
2906
|
+
Header: parsed.header,
|
|
2907
|
+
ContentLength: bodyInfo.contentLength,
|
|
2908
|
+
TransferEncoding: bodyInfo.transferEncoding,
|
|
2909
|
+
Close: shouldClose(protoMajor, protoMinor, parsed.header),
|
|
2910
|
+
Request: req,
|
|
2911
|
+
}),
|
|
2912
|
+
null,
|
|
2913
|
+
]
|
|
2715
2914
|
}
|
|
2716
2915
|
|
|
2717
|
-
|
|
2718
|
-
|
|
2916
|
+
async function readHTTPWire(reader: any): Promise<[string, $.GoError]> {
|
|
2917
|
+
const r = $.pointerValueOrNil<any>(reader)
|
|
2918
|
+
if (r == null || typeof r.Read !== 'function') {
|
|
2919
|
+
return ['', errors.New('malformed HTTP message')]
|
|
2920
|
+
}
|
|
2921
|
+
const [data, err] = await io.ReadAll(r)
|
|
2922
|
+
if (err != null) {
|
|
2923
|
+
return ['', err]
|
|
2924
|
+
}
|
|
2925
|
+
return [$.bytesToString(data), null]
|
|
2719
2926
|
}
|
|
2720
2927
|
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2928
|
+
function splitRequestLine(line: string): [string, string, string] {
|
|
2929
|
+
const first = line.indexOf(' ')
|
|
2930
|
+
const last = line.lastIndexOf(' ')
|
|
2931
|
+
if (first <= 0 || last <= first) {
|
|
2932
|
+
return ['', '', '']
|
|
2933
|
+
}
|
|
2934
|
+
return [
|
|
2935
|
+
line.slice(0, first),
|
|
2936
|
+
line.slice(first + 1, last),
|
|
2937
|
+
line.slice(last + 1),
|
|
2938
|
+
]
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function parseHTTPWire(wire: string): {
|
|
2942
|
+
startLine: string
|
|
2943
|
+
header: Header
|
|
2944
|
+
body: string
|
|
2945
|
+
error: $.GoError
|
|
2946
|
+
} {
|
|
2947
|
+
const headerEnd = wire.indexOf('\r\n\r\n')
|
|
2948
|
+
const separatorLength = headerEnd >= 0 ? 4 : 2
|
|
2949
|
+
const fallbackHeaderEnd = headerEnd >= 0 ? headerEnd : wire.indexOf('\n\n')
|
|
2950
|
+
if (fallbackHeaderEnd < 0) {
|
|
2951
|
+
return {
|
|
2952
|
+
startLine: '',
|
|
2953
|
+
header: new Header(),
|
|
2954
|
+
body: '',
|
|
2955
|
+
error: badHTTPMessageError('malformed HTTP message'),
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
const head = wire.slice(0, fallbackHeaderEnd).replace(/\r\n/g, '\n')
|
|
2959
|
+
const lines = head.split('\n')
|
|
2960
|
+
const startLine = lines.shift() ?? ''
|
|
2961
|
+
const header = new Header()
|
|
2962
|
+
let lastKey = ''
|
|
2963
|
+
for (const rawLine of lines) {
|
|
2964
|
+
if (rawLine === '') {
|
|
2965
|
+
continue
|
|
2966
|
+
}
|
|
2967
|
+
if ((rawLine[0] === ' ' || rawLine[0] === '\t') && lastKey !== '') {
|
|
2968
|
+
const values = Array.from(header.get(lastKey) ?? [])
|
|
2969
|
+
values[values.length - 1] =
|
|
2970
|
+
`${values[values.length - 1]} ${rawLine.trim()}`
|
|
2971
|
+
header.set(lastKey, $.arrayToSlice(values))
|
|
2972
|
+
continue
|
|
2973
|
+
}
|
|
2974
|
+
const colon = rawLine.indexOf(':')
|
|
2975
|
+
if (colon <= 0) {
|
|
2976
|
+
return {
|
|
2977
|
+
startLine: '',
|
|
2978
|
+
header: new Header(),
|
|
2979
|
+
body: '',
|
|
2980
|
+
error: badHTTPMessageError('malformed MIME header line'),
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
lastKey = canonicalMIMEHeaderKey(rawLine.slice(0, colon).trim())
|
|
2984
|
+
Header_Add(header, lastKey, rawLine.slice(colon + 1).trim())
|
|
2985
|
+
}
|
|
2986
|
+
return {
|
|
2987
|
+
startLine,
|
|
2988
|
+
header,
|
|
2989
|
+
body: wire.slice(fallbackHeaderEnd + separatorLength),
|
|
2990
|
+
error: null,
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2994
|
+
function parseHTTPBody(
|
|
2995
|
+
header: Header,
|
|
2996
|
+
rawBody: string,
|
|
2997
|
+
): {
|
|
2998
|
+
body: io.ReadCloser
|
|
2999
|
+
contentLength: number
|
|
3000
|
+
transferEncoding: $.Slice<string>
|
|
3001
|
+
error: $.GoError
|
|
3002
|
+
} {
|
|
3003
|
+
const transferEncoding = Header_Get(header, 'Transfer-Encoding')
|
|
3004
|
+
if (transferEncoding.toLowerCase() === 'chunked') {
|
|
3005
|
+
const chunked = decodeChunkedBody(rawBody)
|
|
3006
|
+
if (chunked.error != null) {
|
|
3007
|
+
return {
|
|
3008
|
+
body: NoBody,
|
|
3009
|
+
contentLength: -1,
|
|
3010
|
+
transferEncoding: $.arrayToSlice(['chunked']),
|
|
3011
|
+
error: chunked.error,
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
return {
|
|
3015
|
+
body: bodyFromString(chunked.body),
|
|
3016
|
+
contentLength: -1,
|
|
3017
|
+
transferEncoding: $.arrayToSlice(['chunked']),
|
|
3018
|
+
error: null,
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
const contentLength = Header_Get(header, 'Content-Length')
|
|
3022
|
+
if (contentLength !== '') {
|
|
3023
|
+
const parsedLength = Number.parseInt(contentLength, 10)
|
|
3024
|
+
if (
|
|
3025
|
+
!/^[0-9]+$/.test(contentLength) ||
|
|
3026
|
+
!Number.isSafeInteger(parsedLength)
|
|
3027
|
+
) {
|
|
3028
|
+
return {
|
|
3029
|
+
body: NoBody,
|
|
3030
|
+
contentLength: 0,
|
|
3031
|
+
transferEncoding: null,
|
|
3032
|
+
error: badHTTPMessageError(`bad Content-Length ${contentLength}`),
|
|
3033
|
+
}
|
|
3034
|
+
}
|
|
3035
|
+
return {
|
|
3036
|
+
body: bodyFromString(rawBody.slice(0, parsedLength)),
|
|
3037
|
+
contentLength: parsedLength,
|
|
3038
|
+
transferEncoding: null,
|
|
3039
|
+
error: null,
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
if (rawBody === '') {
|
|
3043
|
+
return {
|
|
3044
|
+
body: NoBody,
|
|
3045
|
+
contentLength: 0,
|
|
3046
|
+
transferEncoding: null,
|
|
3047
|
+
error: null,
|
|
3048
|
+
}
|
|
3049
|
+
}
|
|
3050
|
+
return {
|
|
3051
|
+
body: bodyFromString(rawBody),
|
|
3052
|
+
contentLength: -1,
|
|
3053
|
+
transferEncoding: null,
|
|
3054
|
+
error: null,
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
function decodeChunkedBody(rawBody: string): {
|
|
3059
|
+
body: string
|
|
3060
|
+
error: $.GoError
|
|
3061
|
+
} {
|
|
3062
|
+
let offset = 0
|
|
3063
|
+
let body = ''
|
|
3064
|
+
while (true) {
|
|
3065
|
+
const lineEnd = rawBody.indexOf('\r\n', offset)
|
|
3066
|
+
if (lineEnd < 0) {
|
|
3067
|
+
return { body: '', error: io.ErrUnexpectedEOF }
|
|
3068
|
+
}
|
|
3069
|
+
const sizeText = rawBody.slice(offset, lineEnd).split(';', 1)[0].trim()
|
|
3070
|
+
const size = Number.parseInt(sizeText, 16)
|
|
3071
|
+
if (!/^[0-9A-Fa-f]+$/.test(sizeText) || !Number.isSafeInteger(size)) {
|
|
3072
|
+
return { body: '', error: badHTTPMessageError('invalid chunk length') }
|
|
3073
|
+
}
|
|
3074
|
+
offset = lineEnd + 2
|
|
3075
|
+
if (size === 0) {
|
|
3076
|
+
return { body, error: null }
|
|
3077
|
+
}
|
|
3078
|
+
if (offset + size + 2 > rawBody.length) {
|
|
3079
|
+
return { body: '', error: io.ErrUnexpectedEOF }
|
|
3080
|
+
}
|
|
3081
|
+
body += rawBody.slice(offset, offset + size)
|
|
3082
|
+
offset += size
|
|
3083
|
+
if (rawBody.slice(offset, offset + 2) !== '\r\n') {
|
|
3084
|
+
return { body: '', error: badHTTPMessageError('malformed chunk') }
|
|
3085
|
+
}
|
|
3086
|
+
offset += 2
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3089
|
+
|
|
3090
|
+
function bodyFromString(body: string): io.ReadCloser {
|
|
3091
|
+
return body === '' ? NoBody : new responseBody($.stringToBytes(body))
|
|
3092
|
+
}
|
|
3093
|
+
|
|
3094
|
+
function shouldClose(
|
|
3095
|
+
protoMajor: number,
|
|
3096
|
+
protoMinor: number,
|
|
3097
|
+
header: Header,
|
|
3098
|
+
): boolean {
|
|
3099
|
+
const connection = Header_Get(header, 'Connection').toLowerCase()
|
|
3100
|
+
if (
|
|
3101
|
+
connection
|
|
3102
|
+
.split(',')
|
|
3103
|
+
.map((part) => part.trim())
|
|
3104
|
+
.includes('close')
|
|
3105
|
+
) {
|
|
3106
|
+
return true
|
|
3107
|
+
}
|
|
3108
|
+
if (protoMajor < 1 || (protoMajor === 1 && protoMinor === 0)) {
|
|
3109
|
+
return !connection
|
|
3110
|
+
.split(',')
|
|
3111
|
+
.map((part) => part.trim())
|
|
3112
|
+
.includes('keep-alive')
|
|
3113
|
+
}
|
|
3114
|
+
return false
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
function badHTTPMessageError(message: string): $.GoError {
|
|
3118
|
+
return errors.New(message)
|
|
2726
3119
|
}
|
|
2727
3120
|
|
|
2728
3121
|
export async function ServeContent(
|
|
@@ -2732,20 +3125,168 @@ export async function ServeContent(
|
|
|
2732
3125
|
_modtime: time.Time,
|
|
2733
3126
|
content: io.Reader | null,
|
|
2734
3127
|
): Promise<void> {
|
|
3128
|
+
await serveContent(w, $.pointerValueOrNil(req), _name, content, null)
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
async function serveContent(
|
|
3132
|
+
w: ResponseWriter | null,
|
|
3133
|
+
req: Request | null,
|
|
3134
|
+
name: string,
|
|
3135
|
+
content: io.Reader | null,
|
|
3136
|
+
knownSize: number | null,
|
|
3137
|
+
): Promise<void> {
|
|
3138
|
+
// Browser media seeks depend on FileServer and ServeContent sharing byte-range semantics.
|
|
2735
3139
|
if (content == null) {
|
|
2736
|
-
NotFound(w, req)
|
|
3140
|
+
NotFound(w, req as Request | null)
|
|
2737
3141
|
return
|
|
2738
3142
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
3143
|
+
if (w == null) {
|
|
3144
|
+
return
|
|
3145
|
+
}
|
|
3146
|
+
const header = await w.Header()
|
|
3147
|
+
if (Header_Get(header, 'Content-Type') === '') {
|
|
3148
|
+
const contentType = mime.TypeByExtension(path.Ext(name))
|
|
3149
|
+
if (contentType !== '') {
|
|
3150
|
+
Header_Set(header, 'Content-Type', contentType)
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
const rangeHeader = req?.Header == null ? '' : Header_Get(req.Header, 'Range')
|
|
3154
|
+
if (rangeHeader === '' && knownSize != null) {
|
|
3155
|
+
Header_Set(header, 'Content-Length', String(knownSize))
|
|
3156
|
+
await w.WriteHeader(StatusOK)
|
|
3157
|
+
if (req?.Method !== MethodHead) {
|
|
3158
|
+
await io.Copy(w as unknown as io.Writer, content)
|
|
3159
|
+
}
|
|
3160
|
+
return
|
|
3161
|
+
}
|
|
3162
|
+
|
|
3163
|
+
const seeker = content as io.Reader & Partial<io.Seeker>
|
|
3164
|
+
if (typeof seeker.Seek !== 'function') {
|
|
3165
|
+
const [data, err] = await io.ReadAll(content)
|
|
3166
|
+
if (err != null) {
|
|
3167
|
+
Error(w, err.Error(), StatusInternalServerError)
|
|
3168
|
+
return
|
|
3169
|
+
}
|
|
3170
|
+
const body = data ?? new Uint8Array(0)
|
|
3171
|
+
Header_Set(header, 'Content-Length', String(body.length))
|
|
3172
|
+
await w.WriteHeader(StatusOK)
|
|
3173
|
+
if (req?.Method !== MethodHead) {
|
|
3174
|
+
await w.Write(body)
|
|
3175
|
+
}
|
|
3176
|
+
return
|
|
3177
|
+
}
|
|
3178
|
+
|
|
3179
|
+
let size = knownSize
|
|
3180
|
+
if (size == null) {
|
|
3181
|
+
const [end, err] = await seeker.Seek(0n, io.SeekEnd)
|
|
3182
|
+
if (err != null) {
|
|
3183
|
+
Error(w, err.Error(), StatusInternalServerError)
|
|
3184
|
+
return
|
|
3185
|
+
}
|
|
3186
|
+
size = Number(end)
|
|
3187
|
+
}
|
|
3188
|
+
const [, seekErr] = await seeker.Seek(0n, io.SeekStart)
|
|
3189
|
+
if (seekErr != null) {
|
|
3190
|
+
Error(w, seekErr.Error(), StatusInternalServerError)
|
|
3191
|
+
return
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
Header_Set(header, 'Accept-Ranges', 'bytes')
|
|
3195
|
+
const parsedRange =
|
|
3196
|
+
rangeHeader === '' ? null : parseHTTPRange(rangeHeader, size)
|
|
3197
|
+
if (parsedRange?.error === true) {
|
|
3198
|
+
Header_Set(header, 'Content-Range', `bytes */${size}`)
|
|
3199
|
+
Header_Set(header, 'Content-Length', '0')
|
|
3200
|
+
await w.WriteHeader(StatusRequestedRangeNotSatisfiable)
|
|
3201
|
+
return
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
let status = StatusOK
|
|
3205
|
+
let start = 0
|
|
3206
|
+
let length = size
|
|
3207
|
+
if (parsedRange?.range != null) {
|
|
3208
|
+
status = StatusPartialContent
|
|
3209
|
+
start = parsedRange.range.start
|
|
3210
|
+
length = parsedRange.range.length
|
|
3211
|
+
Header_Set(
|
|
3212
|
+
header,
|
|
3213
|
+
'Content-Range',
|
|
3214
|
+
`bytes ${start}-${start + length - 1}/${size}`,
|
|
3215
|
+
)
|
|
3216
|
+
}
|
|
3217
|
+
Header_Set(header, 'Content-Length', String(length))
|
|
3218
|
+
|
|
3219
|
+
const [, rangeSeekErr] = await seeker.Seek(BigInt(start), io.SeekStart)
|
|
3220
|
+
if (rangeSeekErr != null) {
|
|
3221
|
+
Error(w, rangeSeekErr.Error(), StatusInternalServerError)
|
|
2742
3222
|
return
|
|
2743
3223
|
}
|
|
2744
|
-
w
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
3224
|
+
await w.WriteHeader(status)
|
|
3225
|
+
if (req?.Method === MethodHead) {
|
|
3226
|
+
return
|
|
3227
|
+
}
|
|
3228
|
+
if (length === 0) {
|
|
3229
|
+
return
|
|
3230
|
+
}
|
|
3231
|
+
await io.CopyN(w as unknown as io.Writer, seeker, BigInt(length))
|
|
3232
|
+
}
|
|
3233
|
+
|
|
3234
|
+
function parseHTTPRange(
|
|
3235
|
+
header: string,
|
|
3236
|
+
size: number,
|
|
3237
|
+
): { range: httpRange | null; error: boolean } {
|
|
3238
|
+
if (!header.startsWith('bytes=') || header.includes(',')) {
|
|
3239
|
+
return { range: null, error: true }
|
|
3240
|
+
}
|
|
3241
|
+
const spec = header.slice('bytes='.length).trim()
|
|
3242
|
+
const dash = spec.indexOf('-')
|
|
3243
|
+
if (dash < 0) {
|
|
3244
|
+
return { range: null, error: true }
|
|
3245
|
+
}
|
|
3246
|
+
const startText = spec.slice(0, dash).trim()
|
|
3247
|
+
const endText = spec.slice(dash + 1).trim()
|
|
3248
|
+
if (startText === '' && endText === '') {
|
|
3249
|
+
return { range: null, error: true }
|
|
3250
|
+
}
|
|
3251
|
+
if (size < 0) {
|
|
3252
|
+
return { range: null, error: true }
|
|
3253
|
+
}
|
|
3254
|
+
if (startText === '') {
|
|
3255
|
+
const suffixLength = parseHTTPRangeNumber(endText)
|
|
3256
|
+
if (suffixLength == null || suffixLength <= 0) {
|
|
3257
|
+
return { range: null, error: true }
|
|
3258
|
+
}
|
|
3259
|
+
if (size === 0) {
|
|
3260
|
+
return { range: null, error: true }
|
|
3261
|
+
}
|
|
3262
|
+
const length = Math.min(suffixLength, size)
|
|
3263
|
+
return { range: { start: size - length, length }, error: false }
|
|
3264
|
+
}
|
|
3265
|
+
|
|
3266
|
+
const start = parseHTTPRangeNumber(startText)
|
|
3267
|
+
if (start == null || start >= size) {
|
|
3268
|
+
return { range: null, error: true }
|
|
3269
|
+
}
|
|
3270
|
+
let end = size - 1
|
|
3271
|
+
if (endText !== '') {
|
|
3272
|
+
const parsedEnd = parseHTTPRangeNumber(endText)
|
|
3273
|
+
if (parsedEnd == null || parsedEnd < start) {
|
|
3274
|
+
return { range: null, error: true }
|
|
3275
|
+
}
|
|
3276
|
+
end = Math.min(parsedEnd, size - 1)
|
|
3277
|
+
}
|
|
3278
|
+
return { range: { start, length: end - start + 1 }, error: false }
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
function parseHTTPRangeNumber(value: string): number | null {
|
|
3282
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
3283
|
+
return null
|
|
3284
|
+
}
|
|
3285
|
+
const parsed = Number(value)
|
|
3286
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
3287
|
+
return null
|
|
2748
3288
|
}
|
|
3289
|
+
return parsed
|
|
2749
3290
|
}
|
|
2750
3291
|
|
|
2751
3292
|
function readCloserForBody(body: io.Reader | null): io.ReadCloser | null {
|