goscript 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/compiler/gotest/runner.go +98 -0
- package/compiler/gotest/runner_test.go +45 -0
- package/compiler/gotest/testdata/browserapi/browserapi_test.go +36 -0
- package/compiler/lowering.go +227 -11
- package/compiler/override-registry_test.go +50 -0
- package/compiler/protobuf-ts-binding.go +155 -7
- package/compiler/protobuf-ts-binding_test.go +116 -2
- package/compiler/runtime-contract.go +2 -0
- package/compiler/runtime-contract_test.go +1 -0
- package/compiler/semantic-model.go +16 -0
- package/compiler/semantic-model_test.go +38 -0
- package/compiler/skeleton_test.go +477 -16
- package/compiler/typescript-emitter.go +4 -0
- package/dist/gs/builtin/builtin.js +7 -9
- package/dist/gs/builtin/builtin.js.map +1 -1
- package/dist/gs/builtin/defer.js +2 -2
- package/dist/gs/builtin/hostio.js +5 -5
- package/dist/gs/builtin/hostio.js.map +1 -1
- package/dist/gs/builtin/map.js +2 -1
- package/dist/gs/builtin/map.js.map +1 -1
- package/dist/gs/builtin/slice.d.ts +3 -0
- package/dist/gs/builtin/slice.js +39 -0
- package/dist/gs/builtin/slice.js.map +1 -1
- package/dist/gs/builtin/type.js +49 -0
- package/dist/gs/builtin/type.js.map +1 -1
- package/dist/gs/compress/zlib/index.js +5 -2
- package/dist/gs/compress/zlib/index.js.map +1 -1
- package/dist/gs/crypto/aes/index.d.ts +15 -0
- package/dist/gs/crypto/aes/index.js +57 -0
- package/dist/gs/crypto/aes/index.js.map +1 -0
- package/dist/gs/crypto/cipher/index.d.ts +41 -0
- package/dist/gs/crypto/cipher/index.js +255 -0
- package/dist/gs/crypto/cipher/index.js.map +1 -0
- package/dist/gs/crypto/ecdh/index.js +27 -8
- package/dist/gs/crypto/ecdh/index.js.map +1 -1
- package/dist/gs/crypto/ed25519/index.js +3 -3
- package/dist/gs/crypto/ed25519/index.js.map +1 -1
- package/dist/gs/crypto/rand/index.js +6 -3
- package/dist/gs/crypto/rand/index.js.map +1 -1
- package/dist/gs/embed/index.js +9 -3
- package/dist/gs/embed/index.js.map +1 -1
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +33 -0
- package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js +4 -1
- package/dist/gs/github.com/mr-tron/base58/base58/index.js.map +1 -1
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
- package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.d.ts +2 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js +39 -0
- package/dist/gs/golang.org/x/crypto/scrypt/index.js.map +1 -0
- package/dist/gs/hash/fnv/index.js +13 -5
- package/dist/gs/hash/fnv/index.js.map +1 -1
- package/dist/gs/io/fs/glob.d.ts +3 -3
- package/dist/gs/io/fs/glob.js +8 -8
- package/dist/gs/io/fs/glob.js.map +1 -1
- package/dist/gs/io/fs/readdir.d.ts +2 -2
- package/dist/gs/io/fs/readdir.js +13 -74
- package/dist/gs/io/fs/readdir.js.map +1 -1
- package/dist/gs/io/fs/sub.js +4 -4
- package/dist/gs/io/fs/sub.js.map +1 -1
- package/dist/gs/io/fs/walk.js +1 -1
- package/dist/gs/io/fs/walk.js.map +1 -1
- package/dist/gs/io/io.js +18 -2
- package/dist/gs/io/io.js.map +1 -1
- package/dist/gs/maps/iter.js.map +1 -1
- package/dist/gs/maps/maps.js.map +1 -1
- package/dist/gs/mime/index.js +5 -2
- package/dist/gs/mime/index.js.map +1 -1
- package/dist/gs/net/http/httptest/index.js +6 -3
- package/dist/gs/net/http/httptest/index.js.map +1 -1
- package/dist/gs/net/http/index.d.ts +16 -4
- package/dist/gs/net/http/index.js +236 -40
- package/dist/gs/net/http/index.js.map +1 -1
- package/dist/gs/net/http/pprof/index.js.map +1 -1
- package/dist/gs/reflect/iter.js +1 -1
- package/dist/gs/reflect/iter.js.map +1 -1
- package/dist/gs/reflect/type.d.ts +2 -0
- package/dist/gs/reflect/type.js +53 -21
- package/dist/gs/reflect/type.js.map +1 -1
- package/dist/gs/runtime/debug/index.js +2 -1
- package/dist/gs/runtime/debug/index.js.map +1 -1
- package/dist/gs/runtime/pprof/index.js.map +1 -1
- package/dist/gs/runtime/runtime.js +2 -2
- package/dist/gs/runtime/runtime.js.map +1 -1
- package/dist/gs/runtime/trace/index.js.map +1 -1
- package/dist/gs/slices/slices.d.ts +1 -1
- package/dist/gs/slices/slices.js +37 -4
- package/dist/gs/slices/slices.js.map +1 -1
- package/go.mod +2 -2
- package/go.sum +2 -0
- package/gs/builtin/builtin.ts +11 -14
- package/gs/builtin/defer.ts +2 -2
- package/gs/builtin/hostio.test.ts +8 -3
- package/gs/builtin/hostio.ts +5 -7
- package/gs/builtin/map.ts +4 -1
- package/gs/builtin/slice.test.ts +14 -0
- package/gs/builtin/slice.ts +64 -0
- package/gs/builtin/type.ts +72 -0
- package/gs/bytes/bytes.test.ts +14 -13
- package/gs/compress/zlib/index.test.ts +19 -5
- package/gs/compress/zlib/index.ts +16 -7
- package/gs/context/context.test.ts +3 -1
- package/gs/crypto/aes/index.test.ts +120 -0
- package/gs/crypto/aes/index.ts +76 -0
- package/gs/crypto/cipher/index.ts +345 -0
- package/gs/crypto/cipher/meta.json +6 -0
- package/gs/crypto/ecdh/index.test.ts +6 -2
- package/gs/crypto/ecdh/index.ts +49 -12
- package/gs/crypto/ed25519/index.ts +20 -7
- package/gs/crypto/rand/index.ts +6 -3
- package/gs/embed/index.test.ts +3 -3
- package/gs/embed/index.ts +9 -3
- package/gs/fmt/fmt.test.ts +29 -4
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +126 -0
- package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +46 -0
- package/gs/github.com/mr-tron/base58/base58/index.ts +9 -3
- package/gs/github.com/zeebo/blake3/internal/consts/index.test.ts +2 -8
- package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
- package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
- package/gs/golang.org/x/crypto/scrypt/index.test.ts +81 -0
- package/gs/golang.org/x/crypto/scrypt/index.ts +54 -0
- package/gs/golang.org/x/crypto/scrypt/meta.json +5 -0
- package/gs/hash/fnv/index.test.ts +1 -8
- package/gs/hash/fnv/index.ts +27 -10
- package/gs/io/fs/glob.ts +13 -10
- package/gs/io/fs/meta.json +2 -0
- package/gs/io/fs/readdir.test.ts +63 -2
- package/gs/io/fs/readdir.ts +33 -30
- package/gs/io/fs/sub.ts +4 -4
- package/gs/io/fs/walk.ts +1 -1
- package/gs/io/io.test.ts +56 -1
- package/gs/io/io.ts +19 -2
- package/gs/maps/iter.ts +9 -9
- package/gs/maps/maps.ts +4 -4
- package/gs/math/bits/index.test.ts +10 -1
- package/gs/mime/index.test.ts +33 -15
- package/gs/mime/index.ts +9 -2
- package/gs/net/http/httptest/index.test.ts +17 -3
- package/gs/net/http/httptest/index.ts +8 -3
- package/gs/net/http/index.test.ts +645 -123
- package/gs/net/http/index.ts +548 -113
- package/gs/net/http/pprof/index.ts +24 -6
- package/gs/os/file_unix_js.test.ts +22 -0
- package/gs/reflect/iter.ts +4 -2
- package/gs/reflect/map.test.ts +56 -1
- package/gs/reflect/type.ts +76 -37
- package/gs/runtime/debug/index.test.ts +32 -4
- package/gs/runtime/debug/index.ts +5 -2
- package/gs/runtime/pprof/index.test.ts +7 -1
- package/gs/runtime/pprof/index.ts +5 -1
- package/gs/runtime/runtime.test.ts +7 -0
- package/gs/runtime/runtime.ts +2 -4
- package/gs/runtime/trace/index.test.ts +9 -1
- package/gs/runtime/trace/index.ts +5 -1
- package/gs/slices/meta.json +3 -0
- package/gs/slices/slices.test.ts +59 -21
- package/gs/slices/slices.ts +61 -20
- package/gs/strconv/complex.test.ts +17 -3
- package/gs/sync/atomic/doc_64.test.ts +2 -9
- package/gs/sync/sync.test.ts +18 -8
- package/gs/syscall/js/index.test.ts +9 -4
- package/package.json +13 -5
|
@@ -124,12 +124,16 @@ describe('net/http override', () => {
|
|
|
124
124
|
expect(StatusText(StatusUnauthorized)).toBe('Unauthorized')
|
|
125
125
|
expect(StatusText(StatusForbidden)).toBe('Forbidden')
|
|
126
126
|
expect(StatusText(StatusMethodNotAllowed)).toBe('Method Not Allowed')
|
|
127
|
-
expect(StatusText(StatusUnsupportedMediaType)).toBe(
|
|
127
|
+
expect(StatusText(StatusUnsupportedMediaType)).toBe(
|
|
128
|
+
'Unsupported Media Type',
|
|
129
|
+
)
|
|
128
130
|
expect(StatusText(StatusTeapot)).toBe("I'm a teapot")
|
|
129
131
|
expect(StatusText(StatusTooManyRequests)).toBe('Too Many Requests')
|
|
130
132
|
expect(StatusText(StatusBadGateway)).toBe('Bad Gateway')
|
|
131
133
|
expect(StatusText(StatusServiceUnavailable)).toBe('Service Unavailable')
|
|
132
|
-
expect(StatusText(StatusNetworkAuthenticationRequired)).toBe(
|
|
134
|
+
expect(StatusText(StatusNetworkAuthenticationRequired)).toBe(
|
|
135
|
+
'Network Authentication Required',
|
|
136
|
+
)
|
|
133
137
|
expect(StatusText(599)).toBe('')
|
|
134
138
|
Header_Set(resp.Header, 'X-Test', 'ok')
|
|
135
139
|
resp.Body = io.NopCloser(bytes.NewReader($.stringToBytes('body')))
|
|
@@ -164,12 +168,18 @@ describe('net/http override', () => {
|
|
|
164
168
|
'text/plain',
|
|
165
169
|
'charset=utf-8',
|
|
166
170
|
])
|
|
167
|
-
expect(Array.from(Header_Values(cloned, 'Content-Type') ?? [])).toContain(
|
|
168
|
-
|
|
171
|
+
expect(Array.from(Header_Values(cloned, 'Content-Type') ?? [])).toContain(
|
|
172
|
+
'copy',
|
|
173
|
+
)
|
|
174
|
+
expect(
|
|
175
|
+
Array.from(Header_Values(header, 'Content-Type') ?? []),
|
|
176
|
+
).not.toContain('copy')
|
|
169
177
|
|
|
170
178
|
const written = new bytes.Buffer()
|
|
171
179
|
expect(Header_Write(header, written)).toBeNull()
|
|
172
|
-
expect(Buffer.from(written.Bytes()).toString('utf8')).toContain(
|
|
180
|
+
expect(Buffer.from(written.Bytes()).toString('utf8')).toContain(
|
|
181
|
+
'Content-Type: text/plain\r\n',
|
|
182
|
+
)
|
|
173
183
|
|
|
174
184
|
expect(ParseHTTPVersion('HTTP/2.0')).toEqual([2, 0, true])
|
|
175
185
|
expect(ParseHTTPVersion('h2')).toEqual([0, 0, false])
|
|
@@ -195,22 +205,58 @@ describe('net/http override', () => {
|
|
|
195
205
|
expect(req?.URL?.Path).toBe('/path')
|
|
196
206
|
expect(req?.URL?.RawQuery).toBe('q=1')
|
|
197
207
|
|
|
198
|
-
expect(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
208
|
+
expect(
|
|
209
|
+
NewRequestWithContext(
|
|
210
|
+
context.Background(),
|
|
211
|
+
'bad method',
|
|
212
|
+
'https://example.invalid/',
|
|
213
|
+
null,
|
|
214
|
+
)[1]?.Error(),
|
|
215
|
+
).toBe('net/http: invalid method "bad method"')
|
|
216
|
+
expect(
|
|
217
|
+
NewRequestWithContext(
|
|
218
|
+
null,
|
|
219
|
+
MethodGet,
|
|
220
|
+
'https://example.invalid/',
|
|
221
|
+
null,
|
|
222
|
+
)[1]?.Error(),
|
|
223
|
+
).toBe('net/http: nil Context')
|
|
224
|
+
expect(
|
|
225
|
+
NewRequestWithContext(
|
|
226
|
+
context.Background(),
|
|
227
|
+
MethodGet,
|
|
228
|
+
'http://[::1',
|
|
229
|
+
null,
|
|
230
|
+
)[1],
|
|
231
|
+
).not.toBeNull()
|
|
232
|
+
expect(
|
|
233
|
+
NewRequestWithContext(
|
|
234
|
+
context.Background(),
|
|
235
|
+
MethodGet,
|
|
236
|
+
'http://x/%zz',
|
|
237
|
+
null,
|
|
238
|
+
)[1],
|
|
239
|
+
).not.toBeNull()
|
|
240
|
+
|
|
241
|
+
const [bodyReq, bodyReqErr] = NewRequest(
|
|
242
|
+
MethodPost,
|
|
243
|
+
'https://example.invalid/upload',
|
|
244
|
+
bytes.NewReader($.stringToBytes('abc')),
|
|
203
245
|
)
|
|
204
|
-
expect(NewRequestWithContext(context.Background(), MethodGet, 'http://[::1', null)[1]).not.toBeNull()
|
|
205
|
-
expect(NewRequestWithContext(context.Background(), MethodGet, 'http://x/%zz', null)[1]).not.toBeNull()
|
|
206
|
-
|
|
207
|
-
const [bodyReq, bodyReqErr] = NewRequest(MethodPost, 'https://example.invalid/upload', bytes.NewReader($.stringToBytes('abc')))
|
|
208
246
|
expect(bodyReqErr).toBeNull()
|
|
209
247
|
expect(bodyReq?.ContentLength).toBe(3)
|
|
210
|
-
const [stringReq, stringReqErr] = NewRequest(
|
|
248
|
+
const [stringReq, stringReqErr] = NewRequest(
|
|
249
|
+
MethodPost,
|
|
250
|
+
'https://example.invalid/upload',
|
|
251
|
+
strings.NewReader('abcd'),
|
|
252
|
+
)
|
|
211
253
|
expect(stringReqErr).toBeNull()
|
|
212
254
|
expect(stringReq?.ContentLength).toBe(4)
|
|
213
|
-
const [noBodyReq, noBodyErr] = NewRequest(
|
|
255
|
+
const [noBodyReq, noBodyErr] = NewRequest(
|
|
256
|
+
MethodPost,
|
|
257
|
+
'https://example.invalid/upload',
|
|
258
|
+
NoBody,
|
|
259
|
+
)
|
|
214
260
|
expect(noBodyErr).toBeNull()
|
|
215
261
|
expect(noBodyReq?.ContentLength).toBe(0)
|
|
216
262
|
expect(noBodyReq?.Body).toBe(NoBody)
|
|
@@ -218,44 +264,84 @@ describe('net/http override', () => {
|
|
|
218
264
|
|
|
219
265
|
it('applies cross-origin protection checks', () => {
|
|
220
266
|
const protection = NewCrossOriginProtection()
|
|
221
|
-
const [sameOrigin] = NewRequest(
|
|
267
|
+
const [sameOrigin] = NewRequest(
|
|
268
|
+
MethodPost,
|
|
269
|
+
'https://example.invalid/update',
|
|
270
|
+
null,
|
|
271
|
+
)
|
|
222
272
|
Header_Set(sameOrigin!.Header, 'Sec-Fetch-Site', 'same-origin')
|
|
223
273
|
expect(protection.Check(sameOrigin)).toBeNull()
|
|
224
274
|
|
|
225
|
-
const [noHeaders] = NewRequest(
|
|
275
|
+
const [noHeaders] = NewRequest(
|
|
276
|
+
MethodPost,
|
|
277
|
+
'https://example.invalid/update',
|
|
278
|
+
null,
|
|
279
|
+
)
|
|
226
280
|
expect(protection.Check(noHeaders)).toBeNull()
|
|
227
281
|
|
|
228
|
-
const [safe] = NewRequest(
|
|
282
|
+
const [safe] = NewRequest(
|
|
283
|
+
MethodOptions,
|
|
284
|
+
'https://example.invalid/update',
|
|
285
|
+
null,
|
|
286
|
+
)
|
|
229
287
|
Header_Set(safe!.Header, 'Sec-Fetch-Site', 'cross-site')
|
|
230
288
|
expect(protection.Check(safe)).toBeNull()
|
|
231
289
|
|
|
232
|
-
const [matchingOrigin] = NewRequest(
|
|
290
|
+
const [matchingOrigin] = NewRequest(
|
|
291
|
+
MethodPost,
|
|
292
|
+
'https://example.invalid/update',
|
|
293
|
+
null,
|
|
294
|
+
)
|
|
233
295
|
Header_Set(matchingOrigin!.Header, 'Origin', 'https://example.invalid')
|
|
234
296
|
expect(protection.Check(matchingOrigin)).toBeNull()
|
|
235
297
|
|
|
236
|
-
const [crossSite] = NewRequest(
|
|
298
|
+
const [crossSite] = NewRequest(
|
|
299
|
+
MethodPost,
|
|
300
|
+
'https://example.invalid/update',
|
|
301
|
+
null,
|
|
302
|
+
)
|
|
237
303
|
Header_Set(crossSite!.Header, 'Sec-Fetch-Site', 'cross-site')
|
|
238
304
|
expect(protection.Check(crossSite)?.Error()).toContain('Sec-Fetch-Site')
|
|
239
305
|
|
|
240
|
-
const [oldBrowser] = NewRequest(
|
|
306
|
+
const [oldBrowser] = NewRequest(
|
|
307
|
+
MethodPost,
|
|
308
|
+
'https://example.invalid/update',
|
|
309
|
+
null,
|
|
310
|
+
)
|
|
241
311
|
Header_Set(oldBrowser!.Header, 'Origin', 'https://attacker.invalid')
|
|
242
|
-
expect(protection.Check(oldBrowser)?.Error()).toContain(
|
|
312
|
+
expect(protection.Check(oldBrowser)?.Error()).toContain(
|
|
313
|
+
'Origin does not match Host',
|
|
314
|
+
)
|
|
243
315
|
|
|
244
316
|
expect(protection.AddTrustedOrigin('https://trusted.invalid')).toBeNull()
|
|
245
|
-
expect(
|
|
246
|
-
|
|
317
|
+
expect(
|
|
318
|
+
protection.AddTrustedOrigin('https://trusted.invalid/'),
|
|
319
|
+
).not.toBeNull()
|
|
320
|
+
const [trusted] = NewRequest(
|
|
321
|
+
MethodPost,
|
|
322
|
+
'https://example.invalid/update',
|
|
323
|
+
null,
|
|
324
|
+
)
|
|
247
325
|
Header_Set(trusted!.Header, 'Origin', 'https://trusted.invalid')
|
|
248
326
|
Header_Set(trusted!.Header, 'Sec-Fetch-Site', 'cross-site')
|
|
249
327
|
expect(protection.Check(trusted)).toBeNull()
|
|
250
328
|
|
|
251
329
|
protection.AddInsecureBypassPattern('/bypass/')
|
|
252
330
|
protection.AddInsecureBypassPattern('POST /post-only/')
|
|
253
|
-
const [bypass] = NewRequest(
|
|
331
|
+
const [bypass] = NewRequest(
|
|
332
|
+
MethodPost,
|
|
333
|
+
'https://example.invalid/bypass/ok',
|
|
334
|
+
null,
|
|
335
|
+
)
|
|
254
336
|
Header_Set(bypass!.Header, 'Origin', 'https://attacker.invalid')
|
|
255
337
|
Header_Set(bypass!.Header, 'Sec-Fetch-Site', 'cross-site')
|
|
256
338
|
expect(protection.Check(bypass)).toBeNull()
|
|
257
339
|
|
|
258
|
-
const [methodBypass] = NewRequest(
|
|
340
|
+
const [methodBypass] = NewRequest(
|
|
341
|
+
MethodPost,
|
|
342
|
+
'https://example.invalid/post-only/',
|
|
343
|
+
null,
|
|
344
|
+
)
|
|
259
345
|
Header_Set(methodBypass!.Header, 'Origin', 'https://attacker.invalid')
|
|
260
346
|
expect(protection.Check(methodBypass)).toBeNull()
|
|
261
347
|
})
|
|
@@ -269,7 +355,11 @@ describe('net/http override', () => {
|
|
|
269
355
|
w?.WriteHeader(StatusOK)
|
|
270
356
|
},
|
|
271
357
|
})
|
|
272
|
-
const [blocked] = NewRequest(
|
|
358
|
+
const [blocked] = NewRequest(
|
|
359
|
+
MethodPost,
|
|
360
|
+
'https://example.invalid/update',
|
|
361
|
+
null,
|
|
362
|
+
)
|
|
273
363
|
Header_Set(blocked!.Header, 'Sec-Fetch-Site', 'cross-site')
|
|
274
364
|
const blockedWriter = new testResponseWriter()
|
|
275
365
|
|
|
@@ -304,7 +394,9 @@ describe('net/http override', () => {
|
|
|
304
394
|
expect(cookies?.[1]?.Value).toBe('v2')
|
|
305
395
|
|
|
306
396
|
expect(ParseCookie('')[1]?.Error()).toBe('http: blank cookie')
|
|
307
|
-
expect(ParseCookie('missing-equals')[1]?.Error()).toBe(
|
|
397
|
+
expect(ParseCookie('missing-equals')[1]?.Error()).toBe(
|
|
398
|
+
"http: '=' not found in cookie",
|
|
399
|
+
)
|
|
308
400
|
expect(ParseCookie('=v1')[1]?.Error()).toBe('http: invalid cookie name')
|
|
309
401
|
expect(ParseCookie('k1=\\')[1]?.Error()).toBe('http: invalid cookie value')
|
|
310
402
|
|
|
@@ -330,9 +422,13 @@ describe('net/http override', () => {
|
|
|
330
422
|
expect(spaced?.Quoted).toBe(true)
|
|
331
423
|
|
|
332
424
|
expect(ParseSetCookie('')[1]?.Error()).toBe('http: blank cookie')
|
|
333
|
-
expect(ParseSetCookie('missing-equals')[1]?.Error()).toBe(
|
|
425
|
+
expect(ParseSetCookie('missing-equals')[1]?.Error()).toBe(
|
|
426
|
+
"http: '=' not found in cookie",
|
|
427
|
+
)
|
|
334
428
|
expect(ParseSetCookie('=v1')[1]?.Error()).toBe('http: invalid cookie name')
|
|
335
|
-
expect(ParseSetCookie('k1=\\')[1]?.Error()).toBe(
|
|
429
|
+
expect(ParseSetCookie('k1=\\')[1]?.Error()).toBe(
|
|
430
|
+
'http: invalid cookie value',
|
|
431
|
+
)
|
|
336
432
|
})
|
|
337
433
|
|
|
338
434
|
it('exports no-body, limit-reader, and unsupported controller surfaces', async () => {
|
|
@@ -342,34 +438,48 @@ describe('net/http override', () => {
|
|
|
342
438
|
expect(err).toBe(io.EOF)
|
|
343
439
|
expect(NoBody.Close()).toBeNull()
|
|
344
440
|
|
|
345
|
-
const limited = MaxBytesReader(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
p[
|
|
350
|
-
|
|
351
|
-
|
|
441
|
+
const limited = MaxBytesReader(
|
|
442
|
+
null,
|
|
443
|
+
{
|
|
444
|
+
Read: (p: Uint8Array) => {
|
|
445
|
+
p[0] = 1
|
|
446
|
+
if (p.length > 1) {
|
|
447
|
+
p[1] = 2
|
|
448
|
+
}
|
|
449
|
+
return [1, null]
|
|
450
|
+
},
|
|
451
|
+
Close: () => null,
|
|
352
452
|
},
|
|
353
|
-
|
|
354
|
-
|
|
453
|
+
1,
|
|
454
|
+
)
|
|
355
455
|
const limitedBuf = new Uint8Array(2)
|
|
356
456
|
expect(limited.Read(limitedBuf)).toEqual([1, null])
|
|
357
457
|
expect(limitedBuf[0]).toBe(1)
|
|
358
458
|
const [, limitErr] = limited.Read(new Uint8Array(1))
|
|
359
459
|
expect(limitErr).toBeInstanceOf(MaxBytesError)
|
|
360
460
|
|
|
361
|
-
const exactReader = MaxBytesReader(
|
|
461
|
+
const exactReader = MaxBytesReader(
|
|
462
|
+
null,
|
|
463
|
+
io.NopCloser(bytes.NewReader($.stringToBytes('ok'))),
|
|
464
|
+
2,
|
|
465
|
+
)
|
|
362
466
|
const [exactData, exactErr] = await io.ReadAll(exactReader)
|
|
363
467
|
expect(exactErr).toBeNull()
|
|
364
468
|
expect(Buffer.from(exactData ?? []).toString('utf8')).toBe('ok')
|
|
365
469
|
|
|
366
|
-
const tooLarge = MaxBytesReader(
|
|
470
|
+
const tooLarge = MaxBytesReader(
|
|
471
|
+
null,
|
|
472
|
+
io.NopCloser(bytes.NewReader($.stringToBytes('toolarge'))),
|
|
473
|
+
2,
|
|
474
|
+
)
|
|
367
475
|
const tooLargeBuf = new Uint8Array(8)
|
|
368
476
|
const [tooLargeN, tooLargeErr] = tooLarge.Read(tooLargeBuf)
|
|
369
477
|
expect(tooLargeN).toBe(2)
|
|
370
478
|
expect(tooLargeErr).toBeInstanceOf(MaxBytesError)
|
|
371
479
|
expect((tooLargeErr as MaxBytesError).Limit).toBe(2)
|
|
372
|
-
expect(Buffer.from(tooLargeBuf.slice(0, tooLargeN)).toString('utf8')).toBe(
|
|
480
|
+
expect(Buffer.from(tooLargeBuf.slice(0, tooLargeN)).toString('utf8')).toBe(
|
|
481
|
+
'to',
|
|
482
|
+
)
|
|
373
483
|
|
|
374
484
|
const controller = NewResponseController(null)
|
|
375
485
|
expect(controller.Hijack()[2]).toBe(ErrNotSupported)
|
|
@@ -382,12 +492,15 @@ describe('net/http override', () => {
|
|
|
382
492
|
const body = io.NopCloser(bytes.NewReader($.stringToBytes('ok')))
|
|
383
493
|
const [req] = NewRequest(MethodPost, 'http://example.invalid/upload', body)
|
|
384
494
|
let servedReq: any = null
|
|
385
|
-
const handler = MaxBytesHandler(
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
495
|
+
const handler = MaxBytesHandler(
|
|
496
|
+
{
|
|
497
|
+
ServeHTTP(_w, r) {
|
|
498
|
+
servedReq = $.pointerValue(r)
|
|
499
|
+
Header_Set(servedReq.Header, 'X-Shared', 'true')
|
|
500
|
+
},
|
|
389
501
|
},
|
|
390
|
-
|
|
502
|
+
1,
|
|
503
|
+
)
|
|
391
504
|
|
|
392
505
|
handler.ServeHTTP(null, req)
|
|
393
506
|
|
|
@@ -424,7 +537,11 @@ describe('net/http override', () => {
|
|
|
424
537
|
throw new Error('network down')
|
|
425
538
|
},
|
|
426
539
|
})
|
|
427
|
-
const [req, reqErr] = NewRequest(
|
|
540
|
+
const [req, reqErr] = NewRequest(
|
|
541
|
+
MethodPost,
|
|
542
|
+
'https://example.invalid',
|
|
543
|
+
null,
|
|
544
|
+
)
|
|
428
545
|
expect(reqErr).toBeNull()
|
|
429
546
|
expect((req!.URL as any).Path).toBe('/')
|
|
430
547
|
expect(req!.Host).toBe('example.invalid')
|
|
@@ -440,13 +557,21 @@ describe('net/http override', () => {
|
|
|
440
557
|
Read: (p: Uint8Array) => [p.length, null] as [number, null],
|
|
441
558
|
}
|
|
442
559
|
|
|
443
|
-
const [req, reqErr] = NewRequest(
|
|
560
|
+
const [req, reqErr] = NewRequest(
|
|
561
|
+
MethodPost,
|
|
562
|
+
'https://example.invalid/upload',
|
|
563
|
+
reader,
|
|
564
|
+
)
|
|
444
565
|
|
|
445
566
|
expect(reqErr).toBeNull()
|
|
446
567
|
expect(req!.Body).not.toBeNull()
|
|
447
568
|
expect(req!.Body!.Close()).toBeNull()
|
|
448
569
|
|
|
449
|
-
const resp = new Response({
|
|
570
|
+
const resp = new Response({
|
|
571
|
+
StatusCode: StatusCreated,
|
|
572
|
+
ContentLength: -1,
|
|
573
|
+
Request: varRef(req!),
|
|
574
|
+
})
|
|
450
575
|
expect(resp.ContentLength).toBe(-1)
|
|
451
576
|
expect((resp.Request as any).value).toBe(req)
|
|
452
577
|
})
|
|
@@ -462,7 +587,9 @@ describe('net/http override', () => {
|
|
|
462
587
|
const headers = init?.headers as Headers
|
|
463
588
|
expect(headers.get('Range')).toBe('bytes=0-9')
|
|
464
589
|
expect(headers.get('Authorization')).toBe('Bearer test')
|
|
465
|
-
expect(
|
|
590
|
+
expect(
|
|
591
|
+
Buffer.from((init?.body as Uint8Array) ?? []).toString('utf8'),
|
|
592
|
+
).toBe('payload')
|
|
466
593
|
return new globalThis.Response('accepted', {
|
|
467
594
|
status: StatusCreated,
|
|
468
595
|
statusText: 'Created',
|
|
@@ -478,7 +605,11 @@ describe('net/http override', () => {
|
|
|
478
605
|
return null
|
|
479
606
|
},
|
|
480
607
|
}
|
|
481
|
-
const [req] = NewRequest(
|
|
608
|
+
const [req] = NewRequest(
|
|
609
|
+
MethodPost,
|
|
610
|
+
'https://example.invalid/upload',
|
|
611
|
+
requestBody,
|
|
612
|
+
)
|
|
482
613
|
Header_Set(req!.Header, 'Range', 'bytes=0-9')
|
|
483
614
|
Header_Set(req!.Header, 'Authorization', 'Bearer test')
|
|
484
615
|
|
|
@@ -491,6 +622,135 @@ describe('net/http override', () => {
|
|
|
491
622
|
expect(requestBodyClosed).toBe(true)
|
|
492
623
|
})
|
|
493
624
|
|
|
625
|
+
it('returns from fetch-backed RoundTrip after response headers', async () => {
|
|
626
|
+
let arrayBufferCalled = false
|
|
627
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
628
|
+
configurable: true,
|
|
629
|
+
writable: true,
|
|
630
|
+
value: async () => {
|
|
631
|
+
const response = new globalThis.Response(null, {
|
|
632
|
+
status: StatusOK,
|
|
633
|
+
statusText: 'OK',
|
|
634
|
+
headers: { 'Content-Length': '5' },
|
|
635
|
+
})
|
|
636
|
+
Object.defineProperty(response, 'arrayBuffer', {
|
|
637
|
+
configurable: true,
|
|
638
|
+
value: () => {
|
|
639
|
+
arrayBufferCalled = true
|
|
640
|
+
return new Promise<ArrayBuffer>(() => {})
|
|
641
|
+
},
|
|
642
|
+
})
|
|
643
|
+
return response
|
|
644
|
+
},
|
|
645
|
+
})
|
|
646
|
+
const [req] = NewRequest(MethodPost, 'https://example.invalid/upload', null)
|
|
647
|
+
|
|
648
|
+
const [resp, err] = await Promise.race([
|
|
649
|
+
DefaultTransport.RoundTrip(req),
|
|
650
|
+
new Promise<never>((_, reject) =>
|
|
651
|
+
setTimeout(() => reject(new Error('RoundTrip did not return')), 25),
|
|
652
|
+
),
|
|
653
|
+
])
|
|
654
|
+
|
|
655
|
+
expect(err).toBeNull()
|
|
656
|
+
expect(resp?.StatusCode).toBe(StatusOK)
|
|
657
|
+
expect(resp?.ContentLength).toBe(5)
|
|
658
|
+
expect(arrayBufferCalled).toBe(false)
|
|
659
|
+
expect(resp?.Body?.Close()).toBeNull()
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('reads fetch-backed response bodies after RoundTrip returns', async () => {
|
|
663
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
664
|
+
configurable: true,
|
|
665
|
+
writable: true,
|
|
666
|
+
value: async () =>
|
|
667
|
+
new globalThis.Response('accepted', {
|
|
668
|
+
status: StatusCreated,
|
|
669
|
+
statusText: 'Created',
|
|
670
|
+
headers: { 'Content-Length': '8' },
|
|
671
|
+
}),
|
|
672
|
+
})
|
|
673
|
+
const [req] = NewRequest(MethodPost, 'https://example.invalid/upload', null)
|
|
674
|
+
|
|
675
|
+
const [resp, err] = await DefaultTransport.RoundTrip(req)
|
|
676
|
+
const buf = new Uint8Array(16)
|
|
677
|
+
const [n, readErr] = await (resp!.Body!.Read(buf) as any)
|
|
678
|
+
|
|
679
|
+
expect(err).toBeNull()
|
|
680
|
+
expect(readErr).toBeNull()
|
|
681
|
+
expect(n).toBe(8)
|
|
682
|
+
expect(Buffer.from(buf.subarray(0, n)).toString('utf8')).toBe('accepted')
|
|
683
|
+
expect(resp?.Body?.Close()).toBeNull()
|
|
684
|
+
})
|
|
685
|
+
|
|
686
|
+
it('aborts pending fetches when the request context is canceled', async () => {
|
|
687
|
+
let fetchSignal: AbortSignal | undefined
|
|
688
|
+
let fetchStarted: (() => void) | null = null
|
|
689
|
+
const fetchStartedPromise = new Promise<void>((resolve) => {
|
|
690
|
+
fetchStarted = resolve
|
|
691
|
+
})
|
|
692
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
693
|
+
configurable: true,
|
|
694
|
+
writable: true,
|
|
695
|
+
value: async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
696
|
+
fetchSignal = init?.signal ?? undefined
|
|
697
|
+
fetchStarted?.()
|
|
698
|
+
return new Promise<globalThis.Response>((_resolve, reject) => {
|
|
699
|
+
fetchSignal?.addEventListener('abort', () => {
|
|
700
|
+
reject(new Error('aborted'))
|
|
701
|
+
})
|
|
702
|
+
})
|
|
703
|
+
},
|
|
704
|
+
})
|
|
705
|
+
const [ctx, cancel] = context.WithCancel(context.Background())
|
|
706
|
+
const [req] = NewRequest(MethodPost, 'https://example.invalid/upload', null)
|
|
707
|
+
|
|
708
|
+
const roundTrip = DefaultTransport.RoundTrip(req!.WithContext(ctx))
|
|
709
|
+
await fetchStartedPromise
|
|
710
|
+
cancel?.()
|
|
711
|
+
const [resp, err] = await roundTrip
|
|
712
|
+
|
|
713
|
+
expect(resp).toBeNull()
|
|
714
|
+
expect(err).toBe(context.Canceled)
|
|
715
|
+
expect(fetchSignal?.aborted).toBe(true)
|
|
716
|
+
})
|
|
717
|
+
|
|
718
|
+
it('aborts pending fetch body reads when the request context is canceled', async () => {
|
|
719
|
+
let fetchSignal: AbortSignal | undefined
|
|
720
|
+
Object.defineProperty(globalThis, 'fetch', {
|
|
721
|
+
configurable: true,
|
|
722
|
+
writable: true,
|
|
723
|
+
value: async (_input: RequestInfo | URL, init?: RequestInit) => {
|
|
724
|
+
fetchSignal = init?.signal ?? undefined
|
|
725
|
+
const response = new globalThis.Response(null, { status: StatusOK })
|
|
726
|
+
Object.defineProperty(response, 'arrayBuffer', {
|
|
727
|
+
configurable: true,
|
|
728
|
+
value: () =>
|
|
729
|
+
new Promise<ArrayBuffer>((_resolve, reject) => {
|
|
730
|
+
fetchSignal?.addEventListener('abort', () => {
|
|
731
|
+
reject(new Error('body aborted'))
|
|
732
|
+
})
|
|
733
|
+
}),
|
|
734
|
+
})
|
|
735
|
+
return response
|
|
736
|
+
},
|
|
737
|
+
})
|
|
738
|
+
const [ctx, cancel] = context.WithCancel(context.Background())
|
|
739
|
+
const [req] = NewRequest(MethodPost, 'https://example.invalid/upload', null)
|
|
740
|
+
|
|
741
|
+
const [resp, roundTripErr] = await DefaultTransport.RoundTrip(
|
|
742
|
+
req!.WithContext(ctx),
|
|
743
|
+
)
|
|
744
|
+
const read = resp!.Body!.Read(new Uint8Array(8)) as any
|
|
745
|
+
cancel?.()
|
|
746
|
+
const [n, readErr] = await read
|
|
747
|
+
|
|
748
|
+
expect(roundTripErr).toBeNull()
|
|
749
|
+
expect(n).toBe(0)
|
|
750
|
+
expect(readErr).toBe(context.Canceled)
|
|
751
|
+
expect(fetchSignal?.aborted).toBe(true)
|
|
752
|
+
})
|
|
753
|
+
|
|
494
754
|
it('closes request bodies when fetch body reads fail', async () => {
|
|
495
755
|
let requestBodyClosed = false
|
|
496
756
|
Object.defineProperty(globalThis, 'fetch', {
|
|
@@ -523,15 +783,20 @@ describe('net/http override', () => {
|
|
|
523
783
|
writable: true,
|
|
524
784
|
value: undefined,
|
|
525
785
|
})
|
|
526
|
-
const [unsupportedReq] = NewRequest(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
786
|
+
const [unsupportedReq] = NewRequest(
|
|
787
|
+
MethodPost,
|
|
788
|
+
'https://example.invalid/upload',
|
|
789
|
+
{
|
|
790
|
+
Read: () => [0, null],
|
|
791
|
+
Close: () => {
|
|
792
|
+
unsupportedClosed = true
|
|
793
|
+
return null
|
|
794
|
+
},
|
|
531
795
|
},
|
|
532
|
-
|
|
796
|
+
)
|
|
533
797
|
|
|
534
|
-
const [unsupportedResp, unsupportedErr] =
|
|
798
|
+
const [unsupportedResp, unsupportedErr] =
|
|
799
|
+
await DefaultTransport.RoundTrip(unsupportedReq)
|
|
535
800
|
|
|
536
801
|
expect(unsupportedResp).toBeNull()
|
|
537
802
|
expect(unsupportedErr?.Error()).toContain('Client.Do is not implemented')
|
|
@@ -547,15 +812,21 @@ describe('net/http override', () => {
|
|
|
547
812
|
})
|
|
548
813
|
const [ctx, cancel] = context.WithCancel(context.Background())
|
|
549
814
|
cancel?.()
|
|
550
|
-
const [canceledReq] = NewRequest(
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
815
|
+
const [canceledReq] = NewRequest(
|
|
816
|
+
MethodPost,
|
|
817
|
+
'https://example.invalid/upload',
|
|
818
|
+
{
|
|
819
|
+
Read: () => [0, null],
|
|
820
|
+
Close: () => {
|
|
821
|
+
canceledClosed = true
|
|
822
|
+
return null
|
|
823
|
+
},
|
|
555
824
|
},
|
|
556
|
-
|
|
825
|
+
)
|
|
557
826
|
|
|
558
|
-
const [canceledResp, canceledErr] = await DefaultTransport.RoundTrip(
|
|
827
|
+
const [canceledResp, canceledErr] = await DefaultTransport.RoundTrip(
|
|
828
|
+
canceledReq!.WithContext(ctx),
|
|
829
|
+
)
|
|
559
830
|
|
|
560
831
|
expect(canceledResp).toBeNull()
|
|
561
832
|
expect(canceledErr).toBe(context.Canceled)
|
|
@@ -670,10 +941,14 @@ describe('net/http override', () => {
|
|
|
670
941
|
|
|
671
942
|
it('posts URL-encoded forms through clients and package helper', async () => {
|
|
672
943
|
const transport = {
|
|
673
|
-
async RoundTrip(
|
|
944
|
+
async RoundTrip(
|
|
945
|
+
got: Request | $.VarRef<Request> | null,
|
|
946
|
+
): Promise<[Response | null, $.GoError]> {
|
|
674
947
|
const request = $.pointerValue<Request>(got)
|
|
675
948
|
expect(request.Method).toBe(MethodPost)
|
|
676
|
-
expect(Header_Get(request.Header, 'Content-Type')).toBe(
|
|
949
|
+
expect(Header_Get(request.Header, 'Content-Type')).toBe(
|
|
950
|
+
'application/x-www-form-urlencoded',
|
|
951
|
+
)
|
|
677
952
|
const [data, err] = await io.ReadAll(request.Body!)
|
|
678
953
|
expect(err).toBeNull()
|
|
679
954
|
expect($.bytesToString(data)).toBe('a=one&a=two&space=x+y')
|
|
@@ -686,7 +961,10 @@ describe('net/http override', () => {
|
|
|
686
961
|
])
|
|
687
962
|
const client = new Client({ Transport: transport })
|
|
688
963
|
|
|
689
|
-
const [clientResp, clientErr] = await client.PostForm(
|
|
964
|
+
const [clientResp, clientErr] = await client.PostForm(
|
|
965
|
+
'https://example.invalid/form',
|
|
966
|
+
form,
|
|
967
|
+
)
|
|
690
968
|
expect(clientErr).toBeNull()
|
|
691
969
|
expect(clientResp?.StatusCode).toBe(StatusOK)
|
|
692
970
|
|
|
@@ -712,7 +990,10 @@ describe('net/http override', () => {
|
|
|
712
990
|
|
|
713
991
|
Header_Add(header, 'x-pack-id', 'pack-1')
|
|
714
992
|
Header_Add(header, 'X-Pack-Id', 'pack-2')
|
|
715
|
-
expect(Array.from(header.get('X-Pack-Id') ?? [])).toEqual([
|
|
993
|
+
expect(Array.from(header.get('X-Pack-Id') ?? [])).toEqual([
|
|
994
|
+
'pack-1',
|
|
995
|
+
'pack-2',
|
|
996
|
+
])
|
|
716
997
|
|
|
717
998
|
Header_Del(header, 'x-pack-id')
|
|
718
999
|
expect(Header_Get(header, 'X-Pack-ID')).toBe('')
|
|
@@ -721,7 +1002,7 @@ describe('net/http override', () => {
|
|
|
721
1002
|
it('accepts server context and shutdown surfaces', () => {
|
|
722
1003
|
const srv = new Server({
|
|
723
1004
|
Addr: ':0',
|
|
724
|
-
BaseContext: () => ({} as any
|
|
1005
|
+
BaseContext: () => ({}) as any,
|
|
725
1006
|
ReadHeaderTimeout: 10,
|
|
726
1007
|
})
|
|
727
1008
|
|
|
@@ -729,7 +1010,9 @@ describe('net/http override', () => {
|
|
|
729
1010
|
expect(srv.BaseContext?.(null)).toEqual({})
|
|
730
1011
|
expect(srv.ReadHeaderTimeout).toBe(10)
|
|
731
1012
|
expect(srv.Shutdown({} as any)).toBeNull()
|
|
732
|
-
expect(srv.ListenAndServeTLS('cert.pem', 'key.pem')?.Error()).toBe(
|
|
1013
|
+
expect(srv.ListenAndServeTLS('cert.pem', 'key.pem')?.Error()).toBe(
|
|
1014
|
+
'net/http: Server.ListenAndServeTLS is not implemented in GoScript',
|
|
1015
|
+
)
|
|
733
1016
|
})
|
|
734
1017
|
|
|
735
1018
|
it('supports handler functions and not-found responses for typechecked server tests', () => {
|
|
@@ -749,6 +1032,116 @@ describe('net/http override', () => {
|
|
|
749
1032
|
expect(writes).toEqual(['status:404', '404 page not found\n'])
|
|
750
1033
|
})
|
|
751
1034
|
|
|
1035
|
+
it('accepts nullable handler function receivers for generated method calls', () => {
|
|
1036
|
+
const writer: ResponseWriter = {
|
|
1037
|
+
Header: () => new Header(),
|
|
1038
|
+
Write: (p) => [p?.length ?? 0, null],
|
|
1039
|
+
WriteHeader: () => undefined,
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
expect(typeof HandlerFunc_ServeHTTP).toBe('function')
|
|
1043
|
+
expect(writer.Header()).toBeInstanceOf(Header)
|
|
1044
|
+
})
|
|
1045
|
+
|
|
1046
|
+
it('registers Handler for generic runtime assertions', () => {
|
|
1047
|
+
const handlerFunc = (_w: ResponseWriter | null, _r: Request | null) =>
|
|
1048
|
+
undefined
|
|
1049
|
+
const handler = $.namedValueInterfaceValue(
|
|
1050
|
+
$.namedFunction(
|
|
1051
|
+
$.functionValue(handlerFunc, {
|
|
1052
|
+
kind: $.TypeKind.Function,
|
|
1053
|
+
params: [
|
|
1054
|
+
'http.ResponseWriter',
|
|
1055
|
+
{
|
|
1056
|
+
kind: $.TypeKind.Pointer,
|
|
1057
|
+
elemType: 'http.Request',
|
|
1058
|
+
},
|
|
1059
|
+
],
|
|
1060
|
+
results: [],
|
|
1061
|
+
}),
|
|
1062
|
+
'http.HandlerFunc',
|
|
1063
|
+
{
|
|
1064
|
+
kind: $.TypeKind.Function,
|
|
1065
|
+
name: 'http.HandlerFunc',
|
|
1066
|
+
params: [
|
|
1067
|
+
'http.ResponseWriter',
|
|
1068
|
+
{
|
|
1069
|
+
kind: $.TypeKind.Pointer,
|
|
1070
|
+
elemType: 'http.Request',
|
|
1071
|
+
},
|
|
1072
|
+
],
|
|
1073
|
+
results: [],
|
|
1074
|
+
},
|
|
1075
|
+
),
|
|
1076
|
+
'http.HandlerFunc',
|
|
1077
|
+
{
|
|
1078
|
+
ServeHTTP: (receiver, ...args) =>
|
|
1079
|
+
HandlerFunc_ServeHTTP(
|
|
1080
|
+
$.isVarRef(receiver) ? receiver.value : receiver,
|
|
1081
|
+
...(args as [ResponseWriter | null, Request | null]),
|
|
1082
|
+
),
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
kind: $.TypeKind.Function,
|
|
1086
|
+
name: 'http.HandlerFunc',
|
|
1087
|
+
params: [
|
|
1088
|
+
'http.ResponseWriter',
|
|
1089
|
+
{
|
|
1090
|
+
kind: $.TypeKind.Pointer,
|
|
1091
|
+
elemType: 'http.Request',
|
|
1092
|
+
},
|
|
1093
|
+
],
|
|
1094
|
+
results: [],
|
|
1095
|
+
},
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
const [asserted, ok] = $.typeAssertTuple<{
|
|
1099
|
+
ServeHTTP(w: ResponseWriter | null, r: Request | null): void
|
|
1100
|
+
}>(handler, 'http.Handler')
|
|
1101
|
+
|
|
1102
|
+
expect(ok).toBe(true)
|
|
1103
|
+
expect(asserted).toBe(handler)
|
|
1104
|
+
})
|
|
1105
|
+
|
|
1106
|
+
it('rejects named method wrappers with incompatible interface arity', () => {
|
|
1107
|
+
const handler = $.namedValueInterfaceValue(
|
|
1108
|
+
$.namedFunction(
|
|
1109
|
+
$.functionValue(() => undefined, {
|
|
1110
|
+
kind: $.TypeKind.Function,
|
|
1111
|
+
params: [],
|
|
1112
|
+
results: [],
|
|
1113
|
+
}),
|
|
1114
|
+
'http.HandlerFunc',
|
|
1115
|
+
{
|
|
1116
|
+
kind: $.TypeKind.Function,
|
|
1117
|
+
name: 'http.HandlerFunc',
|
|
1118
|
+
params: [],
|
|
1119
|
+
results: [],
|
|
1120
|
+
},
|
|
1121
|
+
),
|
|
1122
|
+
'http.HandlerFunc',
|
|
1123
|
+
{
|
|
1124
|
+
ServeHTTP: (receiver, ...args) =>
|
|
1125
|
+
HandlerFunc_ServeHTTP(
|
|
1126
|
+
$.isVarRef(receiver) ? receiver.value : receiver,
|
|
1127
|
+
...(args as [ResponseWriter | null, Request | null]),
|
|
1128
|
+
),
|
|
1129
|
+
},
|
|
1130
|
+
{
|
|
1131
|
+
kind: $.TypeKind.Function,
|
|
1132
|
+
name: 'http.HandlerFunc',
|
|
1133
|
+
params: [],
|
|
1134
|
+
results: [],
|
|
1135
|
+
},
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
const [, ok] = $.typeAssertTuple<{
|
|
1139
|
+
ServeHTTP(w: ResponseWriter | null, r: Request | null): void
|
|
1140
|
+
}>(handler, 'http.Handler')
|
|
1141
|
+
|
|
1142
|
+
expect(ok).toBe(false)
|
|
1143
|
+
})
|
|
1144
|
+
|
|
752
1145
|
it('routes through default mux and handler helper exports', () => {
|
|
753
1146
|
const writes: string[] = []
|
|
754
1147
|
const writer: ResponseWriter = {
|
|
@@ -786,15 +1179,18 @@ describe('net/http override', () => {
|
|
|
786
1179
|
WriteHeader: () => undefined,
|
|
787
1180
|
}
|
|
788
1181
|
|
|
789
|
-
SetCookie(
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
1182
|
+
SetCookie(
|
|
1183
|
+
writer,
|
|
1184
|
+
new Cookie({
|
|
1185
|
+
Name: 'spacewave_local_capability',
|
|
1186
|
+
Value: 'token',
|
|
1187
|
+
Path: '/',
|
|
1188
|
+
MaxAge: 300,
|
|
1189
|
+
HttpOnly: true,
|
|
1190
|
+
Secure: true,
|
|
1191
|
+
SameSite: SameSiteStrictMode,
|
|
1192
|
+
}),
|
|
1193
|
+
)
|
|
798
1194
|
|
|
799
1195
|
expect(Array.from(header.get('Set-Cookie') ?? [])).toEqual([
|
|
800
1196
|
'spacewave_local_capability=token; Path=/; Max-Age=300; HttpOnly; Secure; SameSite=Strict',
|
|
@@ -806,29 +1202,46 @@ describe('net/http override', () => {
|
|
|
806
1202
|
|
|
807
1203
|
expect(err).toBeNull()
|
|
808
1204
|
expect(parsed.Unix()).toBe(784111777)
|
|
809
|
-
expect(DetectContentType($.stringToBytes('<HTML>ok'))).toBe(
|
|
1205
|
+
expect(DetectContentType($.stringToBytes('<HTML>ok'))).toBe(
|
|
1206
|
+
'text/html; charset=utf-8',
|
|
1207
|
+
)
|
|
810
1208
|
expect(
|
|
811
|
-
DetectContentType(
|
|
1209
|
+
DetectContentType(
|
|
1210
|
+
new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
|
1211
|
+
),
|
|
812
1212
|
).toBe('image/png')
|
|
813
|
-
expect(DetectContentType(new Uint8Array([0xff, 0xd8, 0xff, 0x00]))).toBe(
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
expect(DetectContentType($.stringToBytes('
|
|
1213
|
+
expect(DetectContentType(new Uint8Array([0xff, 0xd8, 0xff, 0x00]))).toBe(
|
|
1214
|
+
'image/jpeg',
|
|
1215
|
+
)
|
|
1216
|
+
expect(DetectContentType($.stringToBytes('%PDF-1.7'))).toBe(
|
|
1217
|
+
'application/pdf',
|
|
1218
|
+
)
|
|
1219
|
+
expect(DetectContentType($.stringToBytes('RIFFxxxxWEBPVP'))).toBe(
|
|
1220
|
+
'image/webp',
|
|
1221
|
+
)
|
|
1222
|
+
expect(DetectContentType($.stringToBytes('FORMxxxxAIFF'))).toBe(
|
|
1223
|
+
'audio/aiff',
|
|
1224
|
+
)
|
|
817
1225
|
expect(DetectContentType($.stringToBytes('ID3payload'))).toBe('audio/mpeg')
|
|
818
1226
|
expect(DetectContentType($.stringToBytes('wOFFpayload'))).toBe('font/woff')
|
|
819
|
-
expect(
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
expect(DetectContentType(new Uint8Array())).toBe(
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
1227
|
+
expect(
|
|
1228
|
+
DetectContentType(
|
|
1229
|
+
new Uint8Array([
|
|
1230
|
+
0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f,
|
|
1231
|
+
0x6d, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x70, 0x34, 0x31, 0x00, 0x00,
|
|
1232
|
+
0x00, 0x00,
|
|
1233
|
+
]),
|
|
1234
|
+
),
|
|
1235
|
+
).toBe('video/mp4')
|
|
1236
|
+
expect(DetectContentType(new Uint8Array([0x00, 0x01, 0x02]))).toBe(
|
|
1237
|
+
'application/octet-stream',
|
|
1238
|
+
)
|
|
1239
|
+
expect(DetectContentType(new Uint8Array())).toBe(
|
|
1240
|
+
'text/plain; charset=utf-8',
|
|
1241
|
+
)
|
|
1242
|
+
})
|
|
1243
|
+
|
|
1244
|
+
it('exports file server interface shapes', async () => {
|
|
832
1245
|
const file: File = {
|
|
833
1246
|
Close: () => null,
|
|
834
1247
|
Read: (p) => [p?.length ?? 0, null],
|
|
@@ -837,11 +1250,15 @@ describe('net/http override', () => {
|
|
|
837
1250
|
Stat: () => [null, null],
|
|
838
1251
|
}
|
|
839
1252
|
const fsys: FileSystem = {
|
|
840
|
-
Open: (name) =>
|
|
1253
|
+
Open: (name) =>
|
|
1254
|
+
name === 'ok' ? [file, null] : [null, new Error('missing')],
|
|
841
1255
|
}
|
|
842
1256
|
|
|
843
|
-
|
|
844
|
-
|
|
1257
|
+
const [okFile] = await fsys.Open('ok')
|
|
1258
|
+
const [, missingErr] = await fsys.Open('missing')
|
|
1259
|
+
|
|
1260
|
+
expect(okFile).toBe(file)
|
|
1261
|
+
expect(missingErr?.message).toBe('missing')
|
|
845
1262
|
})
|
|
846
1263
|
|
|
847
1264
|
it('serves files and omits HEAD response bodies', async () => {
|
|
@@ -853,23 +1270,26 @@ describe('net/http override', () => {
|
|
|
853
1270
|
Read: (p: Uint8Array) => reader.Read(p),
|
|
854
1271
|
Seek: (offset: number, whence: number) => reader.Seek(offset, whence),
|
|
855
1272
|
Readdir: () => [null, null] as [null, null],
|
|
856
|
-
Stat: () =>
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
1273
|
+
Stat: () =>
|
|
1274
|
+
[
|
|
1275
|
+
{
|
|
1276
|
+
IsDir: () => false,
|
|
1277
|
+
ModTime: () => null as never,
|
|
1278
|
+
Mode: () => 0,
|
|
1279
|
+
Name: () => 'file.txt',
|
|
1280
|
+
Size: () => 5,
|
|
1281
|
+
Sys: () => null,
|
|
1282
|
+
},
|
|
1283
|
+
null,
|
|
1284
|
+
] as const,
|
|
867
1285
|
}
|
|
868
1286
|
}
|
|
869
1287
|
const root = FS({
|
|
870
1288
|
Open: (name) => {
|
|
871
1289
|
opened.push(name)
|
|
872
|
-
return name === 'file.txt' ?
|
|
1290
|
+
return name === 'file.txt' ?
|
|
1291
|
+
[makeFile(), null]
|
|
1292
|
+
: [null, new Error('missing')]
|
|
873
1293
|
},
|
|
874
1294
|
})
|
|
875
1295
|
const writes: string[] = []
|
|
@@ -883,7 +1303,11 @@ describe('net/http override', () => {
|
|
|
883
1303
|
WriteHeader: (code) => writes.push(`status:${code}`),
|
|
884
1304
|
}
|
|
885
1305
|
const handler = FileServer(root)
|
|
886
|
-
const [getReq] = NewRequest(
|
|
1306
|
+
const [getReq] = NewRequest(
|
|
1307
|
+
MethodGet,
|
|
1308
|
+
'http://example.invalid/../file.txt',
|
|
1309
|
+
null,
|
|
1310
|
+
)
|
|
887
1311
|
|
|
888
1312
|
await handler.ServeHTTP(writer, getReq)
|
|
889
1313
|
|
|
@@ -893,7 +1317,11 @@ describe('net/http override', () => {
|
|
|
893
1317
|
|
|
894
1318
|
writes.length = 0
|
|
895
1319
|
opened.length = 0
|
|
896
|
-
const [headReq] = NewRequest(
|
|
1320
|
+
const [headReq] = NewRequest(
|
|
1321
|
+
MethodHead,
|
|
1322
|
+
'http://example.invalid/file.txt',
|
|
1323
|
+
null,
|
|
1324
|
+
)
|
|
897
1325
|
|
|
898
1326
|
await handler.ServeHTTP(writer, headReq)
|
|
899
1327
|
|
|
@@ -901,6 +1329,76 @@ describe('net/http override', () => {
|
|
|
901
1329
|
expect(writes).toEqual(['status:200'])
|
|
902
1330
|
})
|
|
903
1331
|
|
|
1332
|
+
it('serves files from async file systems and file methods', async () => {
|
|
1333
|
+
const closeCalls: string[] = []
|
|
1334
|
+
const root = {
|
|
1335
|
+
async Open(name: string) {
|
|
1336
|
+
if (name !== 'async.txt') {
|
|
1337
|
+
return [null, new Error('missing')]
|
|
1338
|
+
}
|
|
1339
|
+
let offset = 0
|
|
1340
|
+
const file = {
|
|
1341
|
+
async Close() {
|
|
1342
|
+
closeCalls.push(name)
|
|
1343
|
+
return null
|
|
1344
|
+
},
|
|
1345
|
+
async Read(p) {
|
|
1346
|
+
const data = $.stringToBytes('async-body')
|
|
1347
|
+
if (offset >= data.length) {
|
|
1348
|
+
return [0, io.EOF]
|
|
1349
|
+
}
|
|
1350
|
+
const n = Math.min(p.length, data.length - offset)
|
|
1351
|
+
p.set(data.subarray(offset, offset + n), 0)
|
|
1352
|
+
offset += n
|
|
1353
|
+
return [n, null]
|
|
1354
|
+
},
|
|
1355
|
+
async Seek(seekOffset) {
|
|
1356
|
+
offset = Number(seekOffset)
|
|
1357
|
+
return [offset, null]
|
|
1358
|
+
},
|
|
1359
|
+
async Readdir() {
|
|
1360
|
+
return [null, null]
|
|
1361
|
+
},
|
|
1362
|
+
async Stat() {
|
|
1363
|
+
return [
|
|
1364
|
+
{
|
|
1365
|
+
IsDir: () => false,
|
|
1366
|
+
ModTime: () => null as never,
|
|
1367
|
+
Mode: () => 0,
|
|
1368
|
+
Name: () => name,
|
|
1369
|
+
Size: () => 10,
|
|
1370
|
+
Sys: () => null,
|
|
1371
|
+
},
|
|
1372
|
+
null,
|
|
1373
|
+
]
|
|
1374
|
+
},
|
|
1375
|
+
}
|
|
1376
|
+
return [file, null]
|
|
1377
|
+
},
|
|
1378
|
+
}
|
|
1379
|
+
const writes: string[] = []
|
|
1380
|
+
const header = new Header()
|
|
1381
|
+
const writer: ResponseWriter = {
|
|
1382
|
+
Header: () => header,
|
|
1383
|
+
Write: (p) => {
|
|
1384
|
+
writes.push(Buffer.from(p ?? []).toString('utf8'))
|
|
1385
|
+
return [p?.length ?? 0, null]
|
|
1386
|
+
},
|
|
1387
|
+
WriteHeader: (code) => writes.push(`status:${code}`),
|
|
1388
|
+
}
|
|
1389
|
+
const [req] = NewRequest(
|
|
1390
|
+
MethodGet,
|
|
1391
|
+
'http://example.invalid/async.txt',
|
|
1392
|
+
null,
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
await FileServer(root).ServeHTTP(writer, req)
|
|
1396
|
+
|
|
1397
|
+
expect(writes).toEqual(['status:200', 'async-body'])
|
|
1398
|
+
expect(Header_Get(header, 'Content-Length')).toBe('10')
|
|
1399
|
+
expect(closeCalls).toEqual(['async.txt'])
|
|
1400
|
+
})
|
|
1401
|
+
|
|
904
1402
|
it('awaits ServeContent writes before returning', async () => {
|
|
905
1403
|
const writes: string[] = []
|
|
906
1404
|
const writer: ResponseWriter = {
|
|
@@ -911,15 +1409,35 @@ describe('net/http override', () => {
|
|
|
911
1409
|
},
|
|
912
1410
|
WriteHeader: (code) => writes.push(`status:${code}`),
|
|
913
1411
|
}
|
|
914
|
-
const [req] = NewRequest(
|
|
1412
|
+
const [req] = NewRequest(
|
|
1413
|
+
MethodGet,
|
|
1414
|
+
'http://example.invalid/content.txt',
|
|
1415
|
+
null,
|
|
1416
|
+
)
|
|
915
1417
|
|
|
916
|
-
await ServeContent(
|
|
1418
|
+
await ServeContent(
|
|
1419
|
+
writer,
|
|
1420
|
+
req,
|
|
1421
|
+
'content.txt',
|
|
1422
|
+
null as never,
|
|
1423
|
+
bytes.NewReader($.stringToBytes('served')),
|
|
1424
|
+
)
|
|
917
1425
|
|
|
918
1426
|
expect(writes).toEqual(['status:200', 'served'])
|
|
919
1427
|
|
|
920
1428
|
writes.length = 0
|
|
921
|
-
const [headReq] = NewRequest(
|
|
922
|
-
|
|
1429
|
+
const [headReq] = NewRequest(
|
|
1430
|
+
MethodHead,
|
|
1431
|
+
'http://example.invalid/content.txt',
|
|
1432
|
+
null,
|
|
1433
|
+
)
|
|
1434
|
+
await ServeContent(
|
|
1435
|
+
writer,
|
|
1436
|
+
headReq,
|
|
1437
|
+
'content.txt',
|
|
1438
|
+
null as never,
|
|
1439
|
+
bytes.NewReader($.stringToBytes('hidden')),
|
|
1440
|
+
)
|
|
923
1441
|
|
|
924
1442
|
expect(writes).toEqual(['status:200'])
|
|
925
1443
|
})
|
|
@@ -954,7 +1472,11 @@ describe('net/http override', () => {
|
|
|
954
1472
|
},
|
|
955
1473
|
WriteHeader: (code) => writes.push(`status:${code}`),
|
|
956
1474
|
}
|
|
957
|
-
const [req] = NewRequest(
|
|
1475
|
+
const [req] = NewRequest(
|
|
1476
|
+
MethodGet,
|
|
1477
|
+
'http://example.invalid/eval/missing.js',
|
|
1478
|
+
null,
|
|
1479
|
+
)
|
|
958
1480
|
|
|
959
1481
|
ServeFile(writer, req, '/host-only/missing.js')
|
|
960
1482
|
|