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
@@ -7,6 +7,7 @@ import * as context from '../../context/index.js'
7
7
  import * as io from '../../io/index.js'
8
8
  import * as strings from '../../strings/index.js'
9
9
  import {
10
+ AllowQuerySemicolons,
10
11
  CanonicalHeaderKey,
11
12
  Client,
12
13
  Cookie,
@@ -25,6 +26,8 @@ import {
25
26
  FS,
26
27
  Get,
27
28
  Handle,
29
+ HandleFunc,
30
+ Head,
28
31
  Header,
29
32
  Header_Add,
30
33
  Header_Clone,
@@ -35,10 +38,12 @@ import {
35
38
  Header_Write,
36
39
  HandlerFunc_ServeHTTP,
37
40
  ListenAndServe,
41
+ ListenAndServeTLS,
38
42
  MaxBytesError,
39
43
  MaxBytesHandler,
40
44
  MaxBytesReader,
41
45
  NewFileTransport,
46
+ NewFileTransportFS,
42
47
  MethodGet,
43
48
  MethodHead,
44
49
  MethodOptions,
@@ -50,6 +55,7 @@ import {
50
55
  NewRequest,
51
56
  NewRequestWithContext,
52
57
  NewResponseController,
58
+ NewServeMux,
53
59
  NoBody,
54
60
  NotFound,
55
61
  NotFoundHandler,
@@ -58,8 +64,15 @@ import {
58
64
  ParseSetCookie,
59
65
  ParseTime,
60
66
  PostForm,
67
+ Post,
61
68
  Protocols,
69
+ ProxyFromEnvironment,
70
+ ProxyURL,
62
71
  RegisterInProcessServer,
72
+ ReadRequest,
73
+ ReadResponse,
74
+ Redirect,
75
+ RedirectHandler,
63
76
  SameSiteStrictMode,
64
77
  SetCookie,
65
78
  StatusBadGateway,
@@ -68,12 +81,18 @@ import {
68
81
  ResponseWriter,
69
82
  ServeContent,
70
83
  ServeFile,
84
+ ServeFileFS,
85
+ Serve,
71
86
  Server,
87
+ ServeTLS,
72
88
  StatusCreated,
73
89
  StatusForbidden,
90
+ StatusFound,
74
91
  StatusMethodNotAllowed,
75
92
  StatusNotFound,
76
93
  StatusOK,
94
+ StatusPartialContent,
95
+ StatusRequestedRangeNotSatisfiable,
77
96
  StatusServiceUnavailable,
78
97
  StatusTeapot,
79
98
  StatusText,
@@ -82,7 +101,9 @@ import {
82
101
  StatusUnsupportedMediaType,
83
102
  StatusNetworkAuthenticationRequired,
84
103
  TimeFormat,
104
+ TimeoutHandler,
85
105
  TrailerPrefix,
106
+ StripPrefix,
86
107
  Transport,
87
108
  UnregisterInProcessServer,
88
109
  } from './index.js'
@@ -157,6 +178,181 @@ describe('net/http override', () => {
157
178
  expect(ErrServerClosed.Error()).toBe('http: Server closed')
158
179
  })
159
180
 
181
+ it('parses HTTP/1 request and response wire format like Go', async () => {
182
+ const requestCases = [
183
+ {
184
+ wire:
185
+ 'GET /alpha?x=1 HTTP/1.1\r\n' +
186
+ 'Host: example.com\r\n' +
187
+ 'User-Agent: probe\r\n' +
188
+ 'X-Test: one\r\n' +
189
+ 'X-Test: two\r\n' +
190
+ 'Content-Length: 0\r\n' +
191
+ '\r\n',
192
+ want: {
193
+ method: 'GET',
194
+ requestURI: '/alpha?x=1',
195
+ path: '/alpha',
196
+ rawQuery: 'x=1',
197
+ protoMajor: 1,
198
+ protoMinor: 1,
199
+ host: 'example.com',
200
+ userAgent: 'probe',
201
+ xTestValues: 2,
202
+ contentLength: 0,
203
+ close: false,
204
+ transferEncoding: [],
205
+ body: '',
206
+ },
207
+ },
208
+ {
209
+ wire:
210
+ 'POST /submit HTTP/1.0\r\n' +
211
+ 'Host: example.com\r\n' +
212
+ 'Content-Length: 4\r\n' +
213
+ 'Connection: close\r\n' +
214
+ '\r\n' +
215
+ 'body',
216
+ want: {
217
+ method: 'POST',
218
+ requestURI: '/submit',
219
+ path: '/submit',
220
+ rawQuery: '',
221
+ protoMajor: 1,
222
+ protoMinor: 0,
223
+ host: 'example.com',
224
+ userAgent: '',
225
+ xTestValues: 0,
226
+ contentLength: 4,
227
+ close: true,
228
+ transferEncoding: [],
229
+ body: 'body',
230
+ },
231
+ },
232
+ {
233
+ wire:
234
+ 'POST /chunk HTTP/1.1\r\n' +
235
+ 'Host: example.com\r\n' +
236
+ 'Transfer-Encoding: chunked\r\n' +
237
+ '\r\n' +
238
+ '5\r\nhello\r\n0\r\n\r\n',
239
+ want: {
240
+ method: 'POST',
241
+ requestURI: '/chunk',
242
+ path: '/chunk',
243
+ rawQuery: '',
244
+ protoMajor: 1,
245
+ protoMinor: 1,
246
+ host: 'example.com',
247
+ userAgent: '',
248
+ xTestValues: 0,
249
+ contentLength: -1,
250
+ close: false,
251
+ transferEncoding: ['chunked'],
252
+ body: 'hello',
253
+ },
254
+ },
255
+ ]
256
+
257
+ for (const { wire, want } of requestCases) {
258
+ const [req, err] = await ReadRequest(strings.NewReader(wire))
259
+ const [body, bodyErr] = await io.ReadAll(req!.Body!)
260
+
261
+ expect(err).toBeNull()
262
+ expect(req!.Method).toBe(want.method)
263
+ expect(req!.RequestURI).toBe(want.requestURI)
264
+ expect(req!.URL.Path).toBe(want.path)
265
+ expect(req!.URL.RawQuery).toBe(want.rawQuery)
266
+ expect(req!.ProtoMajor).toBe(want.protoMajor)
267
+ expect(req!.ProtoMinor).toBe(want.protoMinor)
268
+ expect(req!.Host).toBe(want.host)
269
+ expect(req!.UserAgent()).toBe(want.userAgent)
270
+ expect(
271
+ Array.from(Header_Values(req!.Header, 'X-Test') ?? []),
272
+ ).toHaveLength(want.xTestValues)
273
+ expect(req!.ContentLength).toBe(want.contentLength)
274
+ expect(req!.Close).toBe(want.close)
275
+ expect(Array.from(req!.TransferEncoding ?? [])).toEqual(
276
+ want.transferEncoding,
277
+ )
278
+ expect($.bytesToString(body)).toBe(want.body)
279
+ expect(bodyErr).toBeNull()
280
+ }
281
+
282
+ const responseCases = [
283
+ {
284
+ wire:
285
+ 'HTTP/1.1 201 Created\r\n' +
286
+ 'Content-Type: text/plain\r\n' +
287
+ 'Content-Length: 5\r\n' +
288
+ '\r\n' +
289
+ 'hello',
290
+ want: {
291
+ status: '201 Created',
292
+ statusCode: 201,
293
+ protoMajor: 1,
294
+ protoMinor: 1,
295
+ contentType: 'text/plain',
296
+ contentLength: 5,
297
+ close: false,
298
+ transferEncoding: [],
299
+ body: 'hello',
300
+ },
301
+ },
302
+ {
303
+ wire: 'HTTP/1.0 204 No Content\r\nConnection: close\r\n\r\n',
304
+ want: {
305
+ status: '204 No Content',
306
+ statusCode: 204,
307
+ protoMajor: 1,
308
+ protoMinor: 0,
309
+ contentType: '',
310
+ contentLength: 0,
311
+ close: true,
312
+ transferEncoding: [],
313
+ body: '',
314
+ },
315
+ },
316
+ {
317
+ wire:
318
+ 'HTTP/1.1 200 OK\r\n' +
319
+ 'Transfer-Encoding: chunked\r\n' +
320
+ '\r\n' +
321
+ '5\r\nhello\r\n0\r\n\r\n',
322
+ want: {
323
+ status: '200 OK',
324
+ statusCode: 200,
325
+ protoMajor: 1,
326
+ protoMinor: 1,
327
+ contentType: '',
328
+ contentLength: -1,
329
+ close: false,
330
+ transferEncoding: ['chunked'],
331
+ body: 'hello',
332
+ },
333
+ },
334
+ ]
335
+
336
+ for (const { wire, want } of responseCases) {
337
+ const [resp, err] = await ReadResponse(strings.NewReader(wire), null)
338
+ const [body, bodyErr] = await io.ReadAll(resp!.Body!)
339
+
340
+ expect(err).toBeNull()
341
+ expect(resp!.Status).toBe(want.status)
342
+ expect(resp!.StatusCode).toBe(want.statusCode)
343
+ expect(resp!.ProtoMajor).toBe(want.protoMajor)
344
+ expect(resp!.ProtoMinor).toBe(want.protoMinor)
345
+ expect(Header_Get(resp!.Header, 'Content-Type')).toBe(want.contentType)
346
+ expect(resp!.ContentLength).toBe(want.contentLength)
347
+ expect(resp!.Close).toBe(want.close)
348
+ expect(Array.from(resp!.TransferEncoding ?? [])).toEqual(
349
+ want.transferEncoding,
350
+ )
351
+ expect($.bytesToString(body)).toBe(want.body)
352
+ expect(bodyErr).toBeNull()
353
+ }
354
+ })
355
+
160
356
  it('exports common header and protocol utility surfaces', () => {
161
357
  const header = new Header()
162
358
  Header_Add(header, 'content-type', 'text/plain')
@@ -293,6 +489,19 @@ describe('net/http override', () => {
293
489
  expect(noBodyReq?.Body).toBe(NoBody)
294
490
  })
295
491
 
492
+ it('decodes escaped request paths like native net/http', () => {
493
+ const [req, reqErr] = NewRequestWithContext(
494
+ context.Background(),
495
+ MethodGet,
496
+ 'https://example.invalid/files/what%20is%20this.mp4?inline=1',
497
+ null,
498
+ )
499
+
500
+ expect(reqErr).toBeNull()
501
+ expect(req?.URL?.Path).toBe('/files/what is this.mp4')
502
+ expect(req?.URL?.RawQuery).toBe('inline=1')
503
+ })
504
+
296
505
  it('applies cross-origin protection checks', () => {
297
506
  const protection = NewCrossOriginProtection()
298
507
  const [sameOrigin] = NewRequest(
@@ -516,6 +725,11 @@ describe('net/http override', () => {
516
725
  expect(controller.Hijack()[2]).toBe(ErrNotSupported)
517
726
  expect(controller.SetReadDeadline({} as any)).toBe(ErrNotSupported)
518
727
  expect(ListenAndServe(':0', null)).toBe(ErrNotSupported)
728
+ expect(ListenAndServeTLS(':0', 'cert.pem', 'key.pem', null)).toBe(
729
+ ErrNotSupported,
730
+ )
731
+ expect(Serve(null, null)).toBe(ErrNotSupported)
732
+ expect(ServeTLS(null, null, 'cert.pem', 'key.pem')).toBe(ErrNotSupported)
519
733
  expect(new Transport().Clone()).toBeInstanceOf(Transport)
520
734
  })
521
735
 
@@ -970,6 +1184,42 @@ describe('net/http override', () => {
970
1184
  expect(resp?.StatusCode).toBe(StatusOK)
971
1185
  })
972
1186
 
1187
+ it('routes package Head and Post helpers through DefaultClient', async () => {
1188
+ const oldTransport = DefaultClient.Transport
1189
+ const seen: string[] = []
1190
+ DefaultClient.Transport = {
1191
+ async RoundTrip(
1192
+ got: Request | $.VarRef<Request> | null,
1193
+ ): Promise<[Response | null, $.GoError]> {
1194
+ const request = $.pointerValue<Request>(got)
1195
+ seen.push(request.Method)
1196
+ if (request.Method === MethodPost) {
1197
+ expect(Header_Get(request.Header, 'Content-Type')).toBe('text/plain')
1198
+ const [data, err] = await io.ReadAll(request.Body!)
1199
+ expect(err).toBeNull()
1200
+ expect($.bytesToString(data)).toBe('payload')
1201
+ }
1202
+ return [new Response({ StatusCode: StatusOK }), null]
1203
+ },
1204
+ }
1205
+ try {
1206
+ const [headResp, headErr] = await Head('https://example.invalid/head')
1207
+ const [postResp, postErr] = await Post(
1208
+ 'https://example.invalid/post',
1209
+ 'text/plain',
1210
+ strings.NewReader('payload'),
1211
+ )
1212
+
1213
+ expect(headErr).toBeNull()
1214
+ expect(headResp?.StatusCode).toBe(StatusOK)
1215
+ expect(postErr).toBeNull()
1216
+ expect(postResp?.StatusCode).toBe(StatusOK)
1217
+ expect(seen).toEqual([MethodHead, MethodPost])
1218
+ } finally {
1219
+ DefaultClient.Transport = oldTransport
1220
+ }
1221
+ })
1222
+
973
1223
  it('posts URL-encoded forms through clients and package helper', async () => {
974
1224
  const transport = {
975
1225
  async RoundTrip(
@@ -1202,6 +1452,148 @@ describe('net/http override', () => {
1202
1452
  ])
1203
1453
  })
1204
1454
 
1455
+ it('covers remaining handler, proxy, redirect, timeout, and FS helpers', async () => {
1456
+ const mux = NewServeMux()
1457
+ const muxWriter = new testResponseWriter()
1458
+ const [muxReq] = NewRequest(MethodGet, 'http://example.invalid/local', null)
1459
+ mux.HandleFunc('/local', (w) => {
1460
+ w?.WriteHeader(StatusOK)
1461
+ w?.Write($.stringToBytes('local mux'))
1462
+ })
1463
+
1464
+ mux.ServeHTTP(muxWriter, muxReq)
1465
+
1466
+ expect(muxWriter.Code).toBe(StatusOK)
1467
+ expect(muxWriter.Body.String()).toBe('local mux')
1468
+
1469
+ const defaultWriter = new testResponseWriter()
1470
+ const [defaultReq] = NewRequest(
1471
+ MethodGet,
1472
+ 'http://example.invalid/phase10-handlefunc',
1473
+ null,
1474
+ )
1475
+ HandleFunc('/phase10-handlefunc', (w) => {
1476
+ w?.WriteHeader(StatusOK)
1477
+ w?.Write($.stringToBytes('default handlefunc'))
1478
+ })
1479
+
1480
+ DefaultServeMux.ServeHTTP(defaultWriter, defaultReq)
1481
+
1482
+ expect(defaultWriter.Code).toBe(StatusOK)
1483
+ expect(defaultWriter.Body.String()).toBe('default handlefunc')
1484
+
1485
+ let strippedPath = ''
1486
+ const [stripReq] = NewRequest(
1487
+ MethodGet,
1488
+ 'http://example.invalid/static/app.js',
1489
+ null,
1490
+ )
1491
+ const stripWriter = new testResponseWriter()
1492
+
1493
+ StripPrefix('/static', {
1494
+ ServeHTTP(_w, r) {
1495
+ strippedPath = $.pointerValue<Request>(r).URL.Path
1496
+ },
1497
+ }).ServeHTTP(stripWriter, stripReq)
1498
+
1499
+ expect(strippedPath).toBe('/app.js')
1500
+ expect(stripReq!.URL.Path).toBe('/static/app.js')
1501
+
1502
+ let rewrittenQuery = ''
1503
+ const [semicolonReq] = NewRequest(
1504
+ MethodGet,
1505
+ 'http://example.invalid/search?a=1;b=2',
1506
+ null,
1507
+ )
1508
+
1509
+ AllowQuerySemicolons({
1510
+ ServeHTTP(_w, r) {
1511
+ rewrittenQuery = $.pointerValue<Request>(r).URL.RawQuery
1512
+ },
1513
+ }).ServeHTTP(null, semicolonReq)
1514
+
1515
+ expect(rewrittenQuery).toBe('a=1&b=2')
1516
+ expect(semicolonReq!.URL.RawQuery).toBe('a=1;b=2')
1517
+
1518
+ const fixedProxy = { href: 'http://proxy.invalid' }
1519
+ expect(ProxyURL(fixedProxy)(null)).toEqual([fixedProxy, null])
1520
+ expect(ProxyFromEnvironment(null)).toEqual([null, null])
1521
+
1522
+ const redirectWriter = new testResponseWriter()
1523
+ const [redirectReq] = NewRequest(
1524
+ MethodGet,
1525
+ 'http://example.invalid/dir/page',
1526
+ null,
1527
+ )
1528
+
1529
+ await RedirectHandler('next?x=1', StatusFound).ServeHTTP(
1530
+ redirectWriter,
1531
+ redirectReq,
1532
+ )
1533
+
1534
+ expect(redirectWriter.Code).toBe(StatusFound)
1535
+ expect(Header_Get(redirectWriter.Header(), 'Location')).toBe(
1536
+ '/dir/next?x=1',
1537
+ )
1538
+ expect(redirectWriter.Body.String()).toContain('Found')
1539
+
1540
+ const directRedirectWriter = new testResponseWriter()
1541
+ await Redirect(directRedirectWriter, redirectReq, '/abs', StatusFound)
1542
+
1543
+ expect(Header_Get(directRedirectWriter.Header(), 'Location')).toBe('/abs')
1544
+
1545
+ const timeoutWriter = new testResponseWriter()
1546
+ TimeoutHandler(null, 1, 'timed out').ServeHTTP(timeoutWriter, redirectReq)
1547
+
1548
+ expect(timeoutWriter.Code).toBe(StatusServiceUnavailable)
1549
+ expect(timeoutWriter.Body.String()).toBe('timed out\n')
1550
+
1551
+ const makeFSFile = () => {
1552
+ const data = $.stringToBytes('fs-body')
1553
+ const reader = bytes.NewReader(data)
1554
+ return {
1555
+ Close: () => null,
1556
+ Read: (p: $.Slice<number>) => reader.Read(p),
1557
+ Stat: () =>
1558
+ [
1559
+ {
1560
+ Name: () => 'asset.txt',
1561
+ Size: () => data.length,
1562
+ Mode: () => 0,
1563
+ ModTime: () => new time.Time(),
1564
+ IsDir: () => false,
1565
+ Sys: () => null,
1566
+ },
1567
+ null,
1568
+ ] as const,
1569
+ }
1570
+ }
1571
+ const fsys = {
1572
+ Open: (name: string) =>
1573
+ name === 'asset.txt' ?
1574
+ [makeFSFile(), null]
1575
+ : [null, new Error('missing')],
1576
+ }
1577
+ const serveFileWriter = new testResponseWriter()
1578
+
1579
+ await ServeFileFS(serveFileWriter, redirectReq, fsys, 'asset.txt')
1580
+
1581
+ expect(serveFileWriter.Code).toBe(StatusOK)
1582
+ expect(serveFileWriter.Body.String()).toBe('fs-body')
1583
+
1584
+ const [fileReq] = NewRequest(
1585
+ MethodGet,
1586
+ 'file:///asset.txt',
1587
+ io.NopCloser(bytes.NewReader($.stringToBytes('request'))),
1588
+ )
1589
+ const [fileResp, fileErr] =
1590
+ await NewFileTransportFS(fsys).RoundTrip(fileReq)
1591
+
1592
+ expect(fileErr).toBeNull()
1593
+ expect(fileResp?.StatusCode).toBe(StatusOK)
1594
+ expect(fileResp?.Body).not.toBeNull()
1595
+ })
1596
+
1205
1597
  it('formats Set-Cookie headers for browser bootstrap routes', async () => {
1206
1598
  const header = new Header()
1207
1599
  const writer: ResponseWriter = {
@@ -1232,7 +1624,7 @@ describe('net/http override', () => {
1232
1624
  const [parsed, err] = ParseTime('Sun, 06 Nov 1994 08:49:37 GMT')
1233
1625
 
1234
1626
  expect(err).toBeNull()
1235
- expect(parsed.Unix()).toBe(784111777)
1627
+ expect(parsed.Unix()).toBe(784111777n)
1236
1628
  expect(DetectContentType($.stringToBytes('<HTML>ok'))).toBe(
1237
1629
  'text/html; charset=utf-8',
1238
1630
  )
@@ -1361,6 +1753,261 @@ describe('net/http override', () => {
1361
1753
  expect(writes).toEqual(['status:200'])
1362
1754
  })
1363
1755
 
1756
+ it('serves files whose request paths contain escaped spaces', async () => {
1757
+ const opened: string[] = []
1758
+ const root = FS({
1759
+ Open: (name) => {
1760
+ opened.push(name)
1761
+ const reader = bytes.NewReader($.stringToBytes('video'))
1762
+ return name === 'what is this.mp4' ?
1763
+ [
1764
+ {
1765
+ Close: () => null,
1766
+ Read: (p: Uint8Array) => reader.Read(p),
1767
+ Seek: (offset: number, whence: number) =>
1768
+ reader.Seek(offset, whence),
1769
+ Readdir: () => [null, null] as [null, null],
1770
+ Stat: () =>
1771
+ [
1772
+ {
1773
+ IsDir: () => false,
1774
+ ModTime: () => null as never,
1775
+ Mode: () => 0,
1776
+ Name: () => name,
1777
+ Size: () => 5,
1778
+ Sys: () => null,
1779
+ },
1780
+ null,
1781
+ ] as const,
1782
+ },
1783
+ null,
1784
+ ]
1785
+ : [null, new Error('missing')]
1786
+ },
1787
+ })
1788
+ const writes: string[] = []
1789
+ const writer: ResponseWriter = {
1790
+ Header: () => new Header(),
1791
+ Write: (p) => {
1792
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1793
+ return [p?.length ?? 0, null]
1794
+ },
1795
+ WriteHeader: (code) => writes.push(`status:${code}`),
1796
+ }
1797
+ const [req] = NewRequest(
1798
+ MethodGet,
1799
+ 'http://example.invalid/what%20is%20this.mp4',
1800
+ null,
1801
+ )
1802
+
1803
+ await FileServer(root).ServeHTTP(writer, req)
1804
+
1805
+ expect(opened).toEqual(['what is this.mp4'])
1806
+ expect(writes).toEqual(['status:200', 'video'])
1807
+ })
1808
+
1809
+ it('serves byte ranges from files like native net/http', async () => {
1810
+ const data = $.stringToBytes('0123456789')
1811
+ let offset = 0
1812
+ const root = {
1813
+ Open: () =>
1814
+ [
1815
+ {
1816
+ Close: () => null,
1817
+ Read: (p: $.Slice<number>) => {
1818
+ if (offset >= data.length) {
1819
+ return [0, io.EOF] as [number, $.GoError]
1820
+ }
1821
+ const n = Math.min(p?.length ?? 0, data.length - offset)
1822
+ p?.set(data.subarray(offset, offset + n), 0)
1823
+ offset += n
1824
+ return [n, null] as [number, $.GoError]
1825
+ },
1826
+ Seek: (seekOffset: bigint, whence: number) => {
1827
+ switch (whence) {
1828
+ case io.SeekStart:
1829
+ offset = Number(seekOffset)
1830
+ break
1831
+ case io.SeekCurrent:
1832
+ offset += Number(seekOffset)
1833
+ break
1834
+ case io.SeekEnd:
1835
+ offset = data.length + Number(seekOffset)
1836
+ break
1837
+ }
1838
+ return [BigInt(offset), null] as [bigint, $.GoError]
1839
+ },
1840
+ Readdir: () => [null, null] as [null, $.GoError],
1841
+ Stat: () =>
1842
+ [
1843
+ {
1844
+ IsDir: () => false,
1845
+ ModTime: () => null as never,
1846
+ Mode: () => 0,
1847
+ Name: () => 'media.mp4',
1848
+ Size: () => BigInt(data.length),
1849
+ Sys: () => null,
1850
+ },
1851
+ null,
1852
+ ] as const,
1853
+ },
1854
+ null,
1855
+ ] as [File, $.GoError],
1856
+ }
1857
+ const header = new Header()
1858
+ const writes: string[] = []
1859
+ const writer: ResponseWriter = {
1860
+ Header: () => header,
1861
+ Write: (p) => {
1862
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1863
+ return [p?.length ?? 0, null]
1864
+ },
1865
+ WriteHeader: (code) => writes.push(`status:${code}`),
1866
+ }
1867
+ const [req] = NewRequest(
1868
+ MethodGet,
1869
+ 'http://example.invalid/media.mp4',
1870
+ null,
1871
+ )
1872
+ Header_Set(req!.Header, 'Range', 'bytes=2-5')
1873
+
1874
+ await FileServer(root).ServeHTTP(writer, req)
1875
+
1876
+ expect(writes).toEqual([`status:${StatusPartialContent}`, '2345'])
1877
+ expect(Header_Get(header, 'Accept-Ranges')).toBe('bytes')
1878
+ expect(Header_Get(header, 'Content-Length')).toBe('4')
1879
+ expect(Header_Get(header, 'Content-Range')).toBe('bytes 2-5/10')
1880
+ expect(Header_Get(header, 'Content-Type')).toBe('video/mp4')
1881
+ })
1882
+
1883
+ it('serves open-ended and suffix byte ranges from FileServer', async () => {
1884
+ const serveRange = async (
1885
+ range: string,
1886
+ ): Promise<{
1887
+ body: string
1888
+ code: number
1889
+ contentRange: string
1890
+ length: string
1891
+ }> => {
1892
+ const reader = bytes.NewReader($.stringToBytes('0123456789'))
1893
+ const root = {
1894
+ Open: () =>
1895
+ [
1896
+ {
1897
+ Close: () => null,
1898
+ Read: (p: $.Slice<number>) => reader.Read(p),
1899
+ Seek: (offset: number, whence: number) =>
1900
+ reader.Seek(offset, whence),
1901
+ Readdir: () => [null, null] as [null, $.GoError],
1902
+ Stat: () =>
1903
+ [
1904
+ {
1905
+ IsDir: () => false,
1906
+ ModTime: () => null as never,
1907
+ Mode: () => 0,
1908
+ Name: () => 'range.txt',
1909
+ Size: () => 10,
1910
+ Sys: () => null,
1911
+ },
1912
+ null,
1913
+ ] as const,
1914
+ },
1915
+ null,
1916
+ ] as [File, $.GoError],
1917
+ }
1918
+ let code = 0
1919
+ const header = new Header()
1920
+ const body: string[] = []
1921
+ const writer: ResponseWriter = {
1922
+ Header: () => header,
1923
+ Write: (p) => {
1924
+ body.push(Buffer.from(p ?? []).toString('utf8'))
1925
+ return [p?.length ?? 0, null]
1926
+ },
1927
+ WriteHeader: (statusCode) => {
1928
+ code = statusCode
1929
+ },
1930
+ }
1931
+ const [req] = NewRequest(
1932
+ MethodGet,
1933
+ 'http://example.invalid/range.txt',
1934
+ null,
1935
+ )
1936
+ Header_Set(req!.Header, 'Range', range)
1937
+
1938
+ await FileServer(root).ServeHTTP(writer, req)
1939
+
1940
+ return {
1941
+ body: body.join(''),
1942
+ code,
1943
+ contentRange: Header_Get(header, 'Content-Range'),
1944
+ length: Header_Get(header, 'Content-Length'),
1945
+ }
1946
+ }
1947
+
1948
+ await expect(serveRange('bytes=6-')).resolves.toEqual({
1949
+ body: '6789',
1950
+ code: StatusPartialContent,
1951
+ contentRange: 'bytes 6-9/10',
1952
+ length: '4',
1953
+ })
1954
+ await expect(serveRange('bytes=-4')).resolves.toEqual({
1955
+ body: '6789',
1956
+ code: StatusPartialContent,
1957
+ contentRange: 'bytes 6-9/10',
1958
+ length: '4',
1959
+ })
1960
+ })
1961
+
1962
+ it('rejects unsatisfiable file ranges like native net/http', async () => {
1963
+ const root = {
1964
+ Open: () =>
1965
+ [
1966
+ {
1967
+ Close: () => null,
1968
+ Read: () => [0, io.EOF] as [number, $.GoError],
1969
+ Seek: () => [0, null] as [number, $.GoError],
1970
+ Readdir: () => [null, null] as [null, $.GoError],
1971
+ Stat: () =>
1972
+ [
1973
+ {
1974
+ IsDir: () => false,
1975
+ ModTime: () => null as never,
1976
+ Mode: () => 0,
1977
+ Name: () => 'range.txt',
1978
+ Size: () => 10,
1979
+ Sys: () => null,
1980
+ },
1981
+ null,
1982
+ ] as const,
1983
+ },
1984
+ null,
1985
+ ] as [File, $.GoError],
1986
+ }
1987
+ const header = new Header()
1988
+ const writes: string[] = []
1989
+ const writer: ResponseWriter = {
1990
+ Header: () => header,
1991
+ Write: (p) => {
1992
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
1993
+ return [p?.length ?? 0, null]
1994
+ },
1995
+ WriteHeader: (code) => writes.push(`status:${code}`),
1996
+ }
1997
+ const [req] = NewRequest(
1998
+ MethodGet,
1999
+ 'http://example.invalid/range.txt',
2000
+ null,
2001
+ )
2002
+ Header_Set(req!.Header, 'Range', 'bytes=99-100')
2003
+
2004
+ await FileServer(root).ServeHTTP(writer, req)
2005
+
2006
+ expect(writes[0]).toBe(`status:${StatusRequestedRangeNotSatisfiable}`)
2007
+ expect(Header_Get(header, 'Content-Range')).toBe('bytes */10')
2008
+ expect(Header_Get(header, 'Content-Length')).toBe('0')
2009
+ })
2010
+
1364
2011
  it('serves files from async file systems and file methods', async () => {
1365
2012
  const closeCalls: string[] = []
1366
2013
  const root = {
@@ -1601,7 +2248,9 @@ describe('net/http override', () => {
1601
2248
 
1602
2249
  expect(readSawHeader).toBe(true)
1603
2250
  expect(writes).toEqual(['status:200', 'streamed-body'])
1604
- expect(Header_Get(header, 'Content-Type')).toBe('text/javascript; charset=utf-8')
2251
+ expect(Header_Get(header, 'Content-Type')).toBe(
2252
+ 'text/javascript; charset=utf-8',
2253
+ )
1605
2254
  })
1606
2255
 
1607
2256
  it('awaits ServeContent writes before returning', async () => {
@@ -1647,6 +2296,38 @@ describe('net/http override', () => {
1647
2296
  expect(writes).toEqual(['status:200'])
1648
2297
  })
1649
2298
 
2299
+ it('serves byte ranges from ServeContent like native net/http', async () => {
2300
+ const writes: string[] = []
2301
+ const header = new Header()
2302
+ const writer: ResponseWriter = {
2303
+ Header: () => header,
2304
+ Write: (p) => {
2305
+ writes.push(Buffer.from(p ?? []).toString('utf8'))
2306
+ return [p?.length ?? 0, null]
2307
+ },
2308
+ WriteHeader: (code) => writes.push(`status:${code}`),
2309
+ }
2310
+ const [req] = NewRequest(
2311
+ MethodGet,
2312
+ 'http://example.invalid/content.txt',
2313
+ null,
2314
+ )
2315
+ Header_Set(req!.Header, 'Range', 'bytes=1-3')
2316
+
2317
+ await ServeContent(
2318
+ writer,
2319
+ req,
2320
+ 'content.txt',
2321
+ null as never,
2322
+ bytes.NewReader($.stringToBytes('served')),
2323
+ )
2324
+
2325
+ expect(writes).toEqual([`status:${StatusPartialContent}`, 'erv'])
2326
+ expect(Header_Get(header, 'Accept-Ranges')).toBe('bytes')
2327
+ expect(Header_Get(header, 'Content-Length')).toBe('3')
2328
+ expect(Header_Get(header, 'Content-Range')).toBe('bytes 1-3/6')
2329
+ })
2330
+
1650
2331
  it('closes request bodies after file transport requests', async () => {
1651
2332
  const root = FS({
1652
2333
  Open: () => [null, new Error('missing')],