goscript 0.2.6 → 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.
Files changed (264) hide show
  1. package/cmd/goscript/cmd-compile.go +7 -0
  2. package/cmd/goscript/cmd_compile_test.go +83 -0
  3. package/compiler/compile-request.go +3 -0
  4. package/compiler/compiler-cache.go +828 -0
  5. package/compiler/compiler-cache_test.go +705 -0
  6. package/compiler/config.go +2 -0
  7. package/compiler/index.test.ts +26 -1
  8. package/compiler/index.ts +5 -0
  9. package/compiler/lowered-program.go +31 -20
  10. package/compiler/lowering.go +349 -93
  11. package/compiler/lowering_bench_test.go +1 -0
  12. package/compiler/override-facts.go +309 -8
  13. package/compiler/override-parity-verifier.go +45 -1
  14. package/compiler/override-parity-verifier_test.go +100 -0
  15. package/compiler/override-registry_test.go +1 -0
  16. package/compiler/package-graph.go +40 -12
  17. package/compiler/package-graph_test.go +29 -0
  18. package/compiler/runtime-contract.go +8 -0
  19. package/compiler/service.go +98 -11
  20. package/compiler/skeleton_test.go +110 -14
  21. package/compiler/typescript-emitter.go +120 -23
  22. package/dist/compiler/index.d.ts +2 -0
  23. package/dist/compiler/index.js +3 -0
  24. package/dist/compiler/index.js.map +1 -1
  25. package/dist/gs/builtin/builtin.d.ts +24 -33
  26. package/dist/gs/builtin/builtin.js +54 -61
  27. package/dist/gs/builtin/builtin.js.map +1 -1
  28. package/dist/gs/builtin/hostio.d.ts +1 -0
  29. package/dist/gs/builtin/hostio.js +1 -1
  30. package/dist/gs/builtin/hostio.js.map +1 -1
  31. package/dist/gs/builtin/index.d.ts +1 -0
  32. package/dist/gs/builtin/index.js +1 -0
  33. package/dist/gs/builtin/index.js.map +1 -1
  34. package/dist/gs/builtin/panic.d.ts +18 -0
  35. package/dist/gs/builtin/panic.js +98 -0
  36. package/dist/gs/builtin/panic.js.map +1 -0
  37. package/dist/gs/builtin/slice.d.ts +10 -0
  38. package/dist/gs/builtin/slice.js +110 -53
  39. package/dist/gs/builtin/slice.js.map +1 -1
  40. package/dist/gs/builtin/type.js +15 -3
  41. package/dist/gs/builtin/type.js.map +1 -1
  42. package/dist/gs/builtin/varRef.d.ts +1 -1
  43. package/dist/gs/builtin/varRef.js +3 -2
  44. package/dist/gs/builtin/varRef.js.map +1 -1
  45. package/dist/gs/bytes/bytes.gs.js +51 -38
  46. package/dist/gs/bytes/bytes.gs.js.map +1 -1
  47. package/dist/gs/bytes/reader.gs.d.ts +1 -1
  48. package/dist/gs/bytes/reader.gs.js +6 -7
  49. package/dist/gs/bytes/reader.gs.js.map +1 -1
  50. package/dist/gs/cmp/index.d.ts +1 -1
  51. package/dist/gs/cmp/index.js +43 -10
  52. package/dist/gs/cmp/index.js.map +1 -1
  53. package/dist/gs/context/context.d.ts +2 -2
  54. package/dist/gs/context/context.js +1 -1
  55. package/dist/gs/context/context.js.map +1 -1
  56. package/dist/gs/embed/index.js +1 -1
  57. package/dist/gs/embed/index.js.map +1 -1
  58. package/dist/gs/encoding/binary/index.js +201 -8
  59. package/dist/gs/encoding/binary/index.js.map +1 -1
  60. package/dist/gs/encoding/json/index.d.ts +5 -0
  61. package/dist/gs/encoding/json/index.js +388 -25
  62. package/dist/gs/encoding/json/index.js.map +1 -1
  63. package/dist/gs/errors/errors.js +17 -24
  64. package/dist/gs/errors/errors.js.map +1 -1
  65. package/dist/gs/fmt/fmt.js +129 -35
  66. package/dist/gs/fmt/fmt.js.map +1 -1
  67. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js +1 -1
  68. package/dist/gs/golang.org/x/crypto/cryptobyte/index.js.map +1 -1
  69. package/dist/gs/internal/bytealg/index.js +43 -8
  70. package/dist/gs/internal/bytealg/index.js.map +1 -1
  71. package/dist/gs/internal/byteorder/index.d.ts +2 -2
  72. package/dist/gs/internal/byteorder/index.js +2 -2
  73. package/dist/gs/internal/byteorder/index.js.map +1 -1
  74. package/dist/gs/io/fs/format.js +2 -2
  75. package/dist/gs/io/fs/format.js.map +1 -1
  76. package/dist/gs/io/fs/fs.d.ts +1 -1
  77. package/dist/gs/io/fs/fs.js +1 -1
  78. package/dist/gs/io/fs/fs.js.map +1 -1
  79. package/dist/gs/io/io.d.ts +21 -21
  80. package/dist/gs/io/io.js +49 -50
  81. package/dist/gs/io/io.js.map +1 -1
  82. package/dist/gs/math/bits/index.js +26 -8
  83. package/dist/gs/math/bits/index.js.map +1 -1
  84. package/dist/gs/math/copysign.gs.js +10 -17
  85. package/dist/gs/math/copysign.gs.js.map +1 -1
  86. package/dist/gs/math/pow.gs.js +5 -0
  87. package/dist/gs/math/pow.gs.js.map +1 -1
  88. package/dist/gs/math/signbit.gs.js +6 -2
  89. package/dist/gs/math/signbit.gs.js.map +1 -1
  90. package/dist/gs/mime/index.js +1 -0
  91. package/dist/gs/mime/index.js.map +1 -1
  92. package/dist/gs/net/http/index.d.ts +6 -6
  93. package/dist/gs/net/http/index.js +507 -43
  94. package/dist/gs/net/http/index.js.map +1 -1
  95. package/dist/gs/os/stat.gs.d.ts +2 -2
  96. package/dist/gs/os/types.gs.d.ts +1 -1
  97. package/dist/gs/os/types.gs.js +1 -1
  98. package/dist/gs/os/types.gs.js.map +1 -1
  99. package/dist/gs/os/types_js.gs.d.ts +1 -1
  100. package/dist/gs/os/types_js.gs.js +7 -7
  101. package/dist/gs/os/types_js.gs.js.map +1 -1
  102. package/dist/gs/os/types_unix.gs.d.ts +1 -1
  103. package/dist/gs/os/types_unix.gs.js +1 -1
  104. package/dist/gs/os/types_unix.gs.js.map +1 -1
  105. package/dist/gs/os/zero_copy_posix.gs.d.ts +1 -1
  106. package/dist/gs/os/zero_copy_posix.gs.js +1 -1
  107. package/dist/gs/os/zero_copy_posix.gs.js.map +1 -1
  108. package/dist/gs/path/filepath/match.js +8 -4
  109. package/dist/gs/path/filepath/match.js.map +1 -1
  110. package/dist/gs/path/filepath/path.js +216 -42
  111. package/dist/gs/path/filepath/path.js.map +1 -1
  112. package/dist/gs/path/match.js +6 -3
  113. package/dist/gs/path/match.js.map +1 -1
  114. package/dist/gs/reflect/type.d.ts +5 -4
  115. package/dist/gs/reflect/type.js +29 -11
  116. package/dist/gs/reflect/type.js.map +1 -1
  117. package/dist/gs/slices/slices.js +11 -11
  118. package/dist/gs/slices/slices.js.map +1 -1
  119. package/dist/gs/strconv/atof.gs.js +156 -43
  120. package/dist/gs/strconv/atof.gs.js.map +1 -1
  121. package/dist/gs/strconv/atoi.gs.d.ts +3 -2
  122. package/dist/gs/strconv/atoi.gs.js +86 -67
  123. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  124. package/dist/gs/strconv/ftoa.gs.js +73 -3
  125. package/dist/gs/strconv/ftoa.gs.js.map +1 -1
  126. package/dist/gs/strconv/itoa.gs.d.ts +4 -4
  127. package/dist/gs/strconv/itoa.gs.js +5 -4
  128. package/dist/gs/strconv/itoa.gs.js.map +1 -1
  129. package/dist/gs/strconv/quote.gs.d.ts +1 -1
  130. package/dist/gs/strconv/quote.gs.js +311 -103
  131. package/dist/gs/strconv/quote.gs.js.map +1 -1
  132. package/dist/gs/strings/reader.d.ts +1 -1
  133. package/dist/gs/strings/reader.js +8 -8
  134. package/dist/gs/strings/reader.js.map +1 -1
  135. package/dist/gs/strings/strings.js +87 -61
  136. package/dist/gs/strings/strings.js.map +1 -1
  137. package/dist/gs/sync/atomic/doc_64.gs.d.ts +14 -14
  138. package/dist/gs/sync/atomic/doc_64.gs.js +10 -10
  139. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  140. package/dist/gs/sync/atomic/type.gs.d.ts +22 -22
  141. package/dist/gs/sync/atomic/type.gs.js +4 -4
  142. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  143. package/dist/gs/sync/sync.js +50 -12
  144. package/dist/gs/sync/sync.js.map +1 -1
  145. package/dist/gs/syscall/fs.d.ts +6 -6
  146. package/dist/gs/syscall/fs.js +1 -1
  147. package/dist/gs/syscall/fs.js.map +1 -1
  148. package/dist/gs/time/time.d.ts +18 -18
  149. package/dist/gs/time/time.js +58 -55
  150. package/dist/gs/time/time.js.map +1 -1
  151. package/dist/gs/unicode/tables.d.ts +11 -0
  152. package/dist/gs/unicode/tables.js +635 -0
  153. package/dist/gs/unicode/tables.js.map +1 -0
  154. package/dist/gs/unicode/unicode.d.ts +58 -38
  155. package/dist/gs/unicode/unicode.js +362 -278
  156. package/dist/gs/unicode/unicode.js.map +1 -1
  157. package/go.sum +13 -0
  158. package/gs/builtin/builtin.ts +83 -93
  159. package/gs/builtin/hostio.ts +1 -1
  160. package/gs/builtin/index.ts +1 -0
  161. package/gs/builtin/panic.test.ts +189 -0
  162. package/gs/builtin/panic.ts +107 -0
  163. package/gs/builtin/runtime-contract.test.ts +5 -5
  164. package/gs/builtin/slice.test.ts +23 -0
  165. package/gs/builtin/slice.ts +133 -95
  166. package/gs/builtin/type.ts +16 -3
  167. package/gs/builtin/varRef.ts +4 -2
  168. package/gs/builtin/wide-int.test.ts +41 -0
  169. package/gs/bytes/bytes.gs.ts +54 -41
  170. package/gs/bytes/bytes.test.ts +18 -1
  171. package/gs/bytes/reader.gs.ts +7 -8
  172. package/gs/cmp/index.test.ts +55 -0
  173. package/gs/cmp/index.ts +45 -9
  174. package/gs/context/context.ts +3 -3
  175. package/gs/embed/index.ts +2 -2
  176. package/gs/encoding/binary/index.test.ts +104 -0
  177. package/gs/encoding/binary/index.ts +259 -11
  178. package/gs/encoding/json/index.test.ts +107 -0
  179. package/gs/encoding/json/index.ts +400 -29
  180. package/gs/errors/errors.test.ts +44 -1
  181. package/gs/errors/errors.ts +15 -31
  182. package/gs/fmt/fmt.test.ts +70 -2
  183. package/gs/fmt/fmt.ts +128 -34
  184. package/gs/golang.org/x/crypto/cryptobyte/index.ts +1 -1
  185. package/gs/internal/bytealg/index.test.ts +26 -1
  186. package/gs/internal/bytealg/index.ts +44 -8
  187. package/gs/internal/byteorder/index.ts +6 -4
  188. package/gs/io/fs/format.ts +2 -2
  189. package/gs/io/fs/fs.ts +2 -2
  190. package/gs/io/fs/stat.test.ts +2 -2
  191. package/gs/io/fs/sub.test.ts +2 -2
  192. package/gs/io/fs/walk.test.ts +2 -2
  193. package/gs/io/io.test.ts +47 -5
  194. package/gs/io/io.ts +73 -73
  195. package/gs/io/limit.test.ts +103 -0
  196. package/gs/math/bits/index.test.ts +128 -0
  197. package/gs/math/bits/index.ts +26 -8
  198. package/gs/math/copysign.gs.test.ts +3 -1
  199. package/gs/math/copysign.gs.ts +10 -22
  200. package/gs/math/pow.gs.test.ts +4 -5
  201. package/gs/math/pow.gs.ts +5 -0
  202. package/gs/math/signbit.gs.test.ts +2 -1
  203. package/gs/math/signbit.gs.ts +6 -3
  204. package/gs/mime/index.ts +1 -0
  205. package/gs/net/http/index.test.ts +683 -2
  206. package/gs/net/http/index.ts +598 -57
  207. package/gs/net/http/meta.json +3 -0
  208. package/gs/os/stat.gs.ts +2 -2
  209. package/gs/os/types.gs.ts +2 -2
  210. package/gs/os/types_js.gs.ts +9 -9
  211. package/gs/os/types_unix.gs.ts +2 -2
  212. package/gs/os/zero_copy_posix.gs.ts +2 -2
  213. package/gs/path/filepath/match.test.ts +16 -0
  214. package/gs/path/filepath/match.ts +8 -4
  215. package/gs/path/filepath/path.test.ts +91 -9
  216. package/gs/path/filepath/path.ts +223 -49
  217. package/gs/path/match.test.ts +32 -0
  218. package/gs/path/match.ts +6 -3
  219. package/gs/reflect/deepequal.test.ts +1 -1
  220. package/gs/reflect/field.test.ts +1 -1
  221. package/gs/reflect/function-types.test.ts +6 -6
  222. package/gs/reflect/sliceat.test.ts +13 -13
  223. package/gs/reflect/structof.test.ts +4 -4
  224. package/gs/reflect/type.ts +34 -14
  225. package/gs/reflect/typefor.test.ts +5 -5
  226. package/gs/runtime/pprof/index.test.ts +20 -0
  227. package/gs/runtime/trace/index.test.ts +3 -0
  228. package/gs/slices/slices.test.ts +31 -0
  229. package/gs/slices/slices.ts +11 -11
  230. package/gs/strconv/append.test.ts +99 -0
  231. package/gs/strconv/atof.gs.ts +156 -42
  232. package/gs/strconv/atof.test.ts +45 -0
  233. package/gs/strconv/atoi.gs.ts +87 -69
  234. package/gs/strconv/atoi.test.ts +49 -0
  235. package/gs/strconv/ftoa.gs.ts +85 -10
  236. package/gs/strconv/ftoa.test.ts +43 -0
  237. package/gs/strconv/itoa.gs.ts +10 -9
  238. package/gs/strconv/quote.gs.ts +335 -108
  239. package/gs/strconv/quote.test.ts +111 -0
  240. package/gs/strings/reader.test.ts +10 -10
  241. package/gs/strings/reader.ts +9 -9
  242. package/gs/strings/strings.test.ts +18 -5
  243. package/gs/strings/strings.ts +81 -68
  244. package/gs/sync/atomic/doc_64.gs.ts +24 -24
  245. package/gs/sync/atomic/doc_64.test.ts +5 -5
  246. package/gs/sync/atomic/type.gs.ts +28 -28
  247. package/gs/sync/sync.test.ts +109 -1
  248. package/gs/sync/sync.ts +46 -12
  249. package/gs/syscall/fs.ts +8 -8
  250. package/gs/syscall/net.test.ts +1 -1
  251. package/gs/time/parse.test.ts +45 -0
  252. package/gs/time/time.test.ts +46 -23
  253. package/gs/time/time.ts +69 -66
  254. package/gs/unicode/gen.go +198 -0
  255. package/gs/unicode/tables.ts +646 -0
  256. package/gs/unicode/unicode.test.ts +69 -0
  257. package/gs/unicode/unicode.ts +396 -312
  258. package/package.json +1 -1
  259. package/dist/gs/github.com/aperturerobotics/util/conc/index.d.ts +0 -20
  260. package/dist/gs/github.com/aperturerobotics/util/conc/index.js +0 -134
  261. package/dist/gs/github.com/aperturerobotics/util/conc/index.js.map +0 -1
  262. package/gs/github.com/aperturerobotics/util/conc/index.test.ts +0 -30
  263. package/gs/github.com/aperturerobotics/util/conc/index.ts +0 -172
  264. 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: number, whence: number): maybePromise<[number, $.GoError]>;
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, _fsys: fs.FS, name: string): void;
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, _r: Request | $.VarRef<Request> | null, url: string, code: number): Promise<void>;
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 ReadRequest(_reader: any): [Request | null, $.GoError];
450
- export declare function ReadResponse(_reader: any, _req: Request | $.VarRef<Request> | null): [Response | null, $.GoError];
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(parsed.pathname, parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search, hasHost ? parsed.protocol.replace(/:$/, '') : '', hasHost ? parsed.host : ''),
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
- () => [0, errors.New('net/http: file does not support seek')]
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
- const header = await w.Header();
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, _fsys, name) {
1321
- ServeFile(w, r, name);
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
- if (req?.URL != null &&
1673
- typeof req.URL.Path === 'string' &&
1674
- req.URL.Path.startsWith(prefix)) {
1675
- req.URL = { ...req.URL, Path: req.URL.Path.slice(prefix.length) || '/' };
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
- return handler?.ServeHTTP(w, req);
1708
+ NotFound(w, req);
1678
1709
  },
1679
1710
  };
1680
1711
  }
1681
1712
  export function AllowQuerySemicolons(handler) {
1682
- return handler ?? NotFoundHandler();
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, _r, url, code) {
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 ProxyURL(fixedURL) {
2157
- return () => [fixedURL, null];
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 ReadRequest(_reader) {
2160
- return [null, ErrNotSupported];
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
- export function ReadResponse(_reader, _req) {
2163
- return [null, ErrNotSupported];
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
- const [data, err] = await io.ReadAll(content);
2171
- if (err != null) {
2172
- Error(w, err.Error(), StatusInternalServerError);
2513
+ if (w == null) {
2173
2514
  return;
2174
2515
  }
2175
- w?.WriteHeader(StatusOK);
2176
- const request = $.pointerValueOrNil(req);
2177
- if (request?.Method !== MethodHead) {
2178
- w?.Write(data);
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) {