goscript 0.1.3 → 0.2.0

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 (330) hide show
  1. package/README.md +5 -2
  2. package/cmd/go_js_wasm_exec/main.go +201 -0
  3. package/cmd/go_js_wasm_exec/main_test.go +83 -0
  4. package/cmd/goscript/{cmd_compile.go → cmd-compile.go} +35 -8
  5. package/cmd/goscript/cmd-test.go +14 -0
  6. package/cmd/goscript/cmd-test_test.go +1 -1
  7. package/cmd/goscript/cmd_compile_test.go +105 -6
  8. package/compiler/build-flags.go +9 -10
  9. package/compiler/compile-request.go +12 -9
  10. package/compiler/compliance_test.go +0 -1
  11. package/compiler/config.go +2 -0
  12. package/compiler/gotest/request.go +28 -0
  13. package/compiler/gotest/runner.go +353 -27
  14. package/compiler/gotest/runner_test.go +400 -1
  15. package/compiler/gotest/testdata/browserapi/browserapi_test.go +20 -0
  16. package/compiler/gotest/testdata/browserapi/go.mod +3 -0
  17. package/compiler/lowered-program.go +24 -17
  18. package/compiler/lowering.go +988 -263
  19. package/compiler/lowering_bench_test.go +364 -0
  20. package/compiler/override-facts.go +15 -0
  21. package/compiler/override-parity-verifier.go +450 -0
  22. package/compiler/override-parity.go +122 -0
  23. package/compiler/override-registry_test.go +559 -0
  24. package/compiler/package-graph.go +61 -4
  25. package/compiler/package-graph_test.go +30 -0
  26. package/compiler/protobuf-ts-binding.go +514 -0
  27. package/compiler/protobuf-ts-binding_test.go +172 -0
  28. package/compiler/semantic-model-types.go +17 -4
  29. package/compiler/semantic-model.go +709 -72
  30. package/compiler/semantic-model_test.go +219 -0
  31. package/compiler/service.go +20 -1
  32. package/compiler/skeleton_test.go +1008 -20
  33. package/compiler/typescript-emitter.go +147 -15
  34. package/dist/gs/builtin/builtin.d.ts +2 -2
  35. package/dist/gs/builtin/builtin.js +20 -0
  36. package/dist/gs/builtin/builtin.js.map +1 -1
  37. package/dist/gs/builtin/slice.d.ts +2 -1
  38. package/dist/gs/builtin/slice.js +34 -4
  39. package/dist/gs/builtin/slice.js.map +1 -1
  40. package/dist/gs/builtin/type.d.ts +14 -6
  41. package/dist/gs/builtin/type.js +224 -64
  42. package/dist/gs/builtin/type.js.map +1 -1
  43. package/dist/gs/builtin/varRef.d.ts +11 -0
  44. package/dist/gs/builtin/varRef.js +57 -2
  45. package/dist/gs/builtin/varRef.js.map +1 -1
  46. package/dist/gs/bytes/buffer.gs.js +1 -1
  47. package/dist/gs/bytes/buffer.gs.js.map +1 -1
  48. package/dist/gs/bytes/reader.gs.js +1 -1
  49. package/dist/gs/bytes/reader.gs.js.map +1 -1
  50. package/dist/gs/compress/zlib/index.d.ts +13 -6
  51. package/dist/gs/compress/zlib/index.js +131 -35
  52. package/dist/gs/compress/zlib/index.js.map +1 -1
  53. package/dist/gs/crypto/sha1/index.js +2 -5
  54. package/dist/gs/crypto/sha1/index.js.map +1 -1
  55. package/dist/gs/crypto/sha256/index.js +2 -5
  56. package/dist/gs/crypto/sha256/index.js.map +1 -1
  57. package/dist/gs/crypto/sha512/index.js +2 -5
  58. package/dist/gs/crypto/sha512/index.js.map +1 -1
  59. package/dist/gs/embed/index.d.ts +6 -0
  60. package/dist/gs/embed/index.js +210 -5
  61. package/dist/gs/embed/index.js.map +1 -1
  62. package/dist/gs/encoding/json/index.d.ts +114 -0
  63. package/dist/gs/encoding/json/index.js +544 -36
  64. package/dist/gs/encoding/json/index.js.map +1 -1
  65. package/dist/gs/fmt/fmt.d.ts +3 -3
  66. package/dist/gs/fmt/fmt.js +29 -16
  67. package/dist/gs/fmt/fmt.js.map +1 -1
  68. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +100 -0
  69. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +564 -0
  70. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  71. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.d.ts +45 -0
  72. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js +229 -0
  73. package/dist/gs/github.com/go-git/go-billy/v6/osfs/index.js.map +1 -0
  74. package/dist/gs/github.com/pkg/errors/errors.js +54 -30
  75. package/dist/gs/github.com/pkg/errors/errors.js.map +1 -1
  76. package/dist/gs/go/scanner/index.d.ts +2 -0
  77. package/dist/gs/go/scanner/index.js +29 -5
  78. package/dist/gs/go/scanner/index.js.map +1 -1
  79. package/dist/gs/go/token/index.js +22 -6
  80. package/dist/gs/go/token/index.js.map +1 -1
  81. package/dist/gs/hash/index.d.ts +6 -0
  82. package/dist/gs/hash/index.js +20 -0
  83. package/dist/gs/hash/index.js.map +1 -1
  84. package/dist/gs/internal/goarch/index.d.ts +43 -3
  85. package/dist/gs/internal/goarch/index.js +42 -10
  86. package/dist/gs/internal/goarch/index.js.map +1 -1
  87. package/dist/gs/io/fs/fs.js +26 -14
  88. package/dist/gs/io/fs/fs.js.map +1 -1
  89. package/dist/gs/io/fs/readdir.js +8 -4
  90. package/dist/gs/io/fs/readdir.js.map +1 -1
  91. package/dist/gs/io/fs/sub.js +8 -1
  92. package/dist/gs/io/fs/sub.js.map +1 -1
  93. package/dist/gs/io/io.d.ts +12 -6
  94. package/dist/gs/io/io.js +87 -42
  95. package/dist/gs/io/io.js.map +1 -1
  96. package/dist/gs/math/bits/index.d.ts +31 -5
  97. package/dist/gs/math/bits/index.js +29 -28
  98. package/dist/gs/math/bits/index.js.map +1 -1
  99. package/dist/gs/mime/index.d.ts +16 -0
  100. package/dist/gs/mime/index.js +315 -6
  101. package/dist/gs/mime/index.js.map +1 -1
  102. package/dist/gs/net/http/httptest/index.d.ts +12 -0
  103. package/dist/gs/net/http/httptest/index.js +85 -6
  104. package/dist/gs/net/http/httptest/index.js.map +1 -1
  105. package/dist/gs/net/http/index.d.ts +303 -6
  106. package/dist/gs/net/http/index.js +1615 -58
  107. package/dist/gs/net/http/index.js.map +1 -1
  108. package/dist/gs/os/dir_unix.gs.js +1 -1
  109. package/dist/gs/os/dir_unix.gs.js.map +1 -1
  110. package/dist/gs/os/error.gs.js +1 -1
  111. package/dist/gs/os/error.gs.js.map +1 -1
  112. package/dist/gs/os/exec.gs.d.ts +1 -0
  113. package/dist/gs/os/exec.gs.js +4 -8
  114. package/dist/gs/os/exec.gs.js.map +1 -1
  115. package/dist/gs/os/exec_posix.gs.js +1 -1
  116. package/dist/gs/os/exec_posix.gs.js.map +1 -1
  117. package/dist/gs/os/index.d.ts +1 -1
  118. package/dist/gs/os/index.js +1 -1
  119. package/dist/gs/os/index.js.map +1 -1
  120. package/dist/gs/os/proc.gs.d.ts +4 -0
  121. package/dist/gs/os/proc.gs.js +12 -6
  122. package/dist/gs/os/proc.gs.js.map +1 -1
  123. package/dist/gs/os/root_js.gs.js +1 -1
  124. package/dist/gs/os/root_js.gs.js.map +1 -1
  125. package/dist/gs/os/types.gs.js +1 -1
  126. package/dist/gs/os/types.gs.js.map +1 -1
  127. package/dist/gs/os/types_js.gs.d.ts +6 -2
  128. package/dist/gs/os/types_js.gs.js +170 -9
  129. package/dist/gs/os/types_js.gs.js.map +1 -1
  130. package/dist/gs/os/types_unix.gs.js +1 -1
  131. package/dist/gs/os/types_unix.gs.js.map +1 -1
  132. package/dist/gs/path/path.js +11 -7
  133. package/dist/gs/path/path.js.map +1 -1
  134. package/dist/gs/reflect/index.d.ts +5 -4
  135. package/dist/gs/reflect/index.js +4 -3
  136. package/dist/gs/reflect/index.js.map +1 -1
  137. package/dist/gs/reflect/map.js +15 -0
  138. package/dist/gs/reflect/map.js.map +1 -1
  139. package/dist/gs/reflect/type.d.ts +26 -6
  140. package/dist/gs/reflect/type.js +1498 -279
  141. package/dist/gs/reflect/type.js.map +1 -1
  142. package/dist/gs/reflect/types.d.ts +14 -6
  143. package/dist/gs/reflect/types.js +35 -1
  144. package/dist/gs/reflect/types.js.map +1 -1
  145. package/dist/gs/reflect/value.d.ts +1 -0
  146. package/dist/gs/reflect/value.js +83 -41
  147. package/dist/gs/reflect/value.js.map +1 -1
  148. package/dist/gs/reflect/visiblefields.js +4 -140
  149. package/dist/gs/reflect/visiblefields.js.map +1 -1
  150. package/dist/gs/runtime/pprof/index.d.ts +8 -2
  151. package/dist/gs/runtime/pprof/index.js +50 -30
  152. package/dist/gs/runtime/pprof/index.js.map +1 -1
  153. package/dist/gs/runtime/runtime.js +5 -4
  154. package/dist/gs/runtime/runtime.js.map +1 -1
  155. package/dist/gs/runtime/trace/index.js +5 -19
  156. package/dist/gs/runtime/trace/index.js.map +1 -1
  157. package/dist/gs/strconv/atoi.gs.js +1 -1
  158. package/dist/gs/strconv/atoi.gs.js.map +1 -1
  159. package/dist/gs/strconv/complex.gs.d.ts +3 -0
  160. package/dist/gs/strconv/complex.gs.js +148 -0
  161. package/dist/gs/strconv/complex.gs.js.map +1 -0
  162. package/dist/gs/strconv/index.d.ts +1 -0
  163. package/dist/gs/strconv/index.js +1 -0
  164. package/dist/gs/strconv/index.js.map +1 -1
  165. package/dist/gs/strings/builder.js +1 -1
  166. package/dist/gs/strings/reader.d.ts +1 -1
  167. package/dist/gs/strings/reader.js +11 -7
  168. package/dist/gs/strings/reader.js.map +1 -1
  169. package/dist/gs/strings/replace.js +15 -7
  170. package/dist/gs/strings/replace.js.map +1 -1
  171. package/dist/gs/strings/strings.d.ts +5 -0
  172. package/dist/gs/strings/strings.js +57 -5
  173. package/dist/gs/strings/strings.js.map +1 -1
  174. package/dist/gs/sync/atomic/type.gs.js +9 -9
  175. package/dist/gs/sync/atomic/type.gs.js.map +1 -1
  176. package/dist/gs/sync/atomic/value.gs.js +2 -2
  177. package/dist/gs/sync/atomic/value.gs.js.map +1 -1
  178. package/dist/gs/sync/sync.d.ts +2 -1
  179. package/dist/gs/sync/sync.js +37 -16
  180. package/dist/gs/sync/sync.js.map +1 -1
  181. package/dist/gs/syscall/env.js +22 -14
  182. package/dist/gs/syscall/env.js.map +1 -1
  183. package/dist/gs/syscall/js/index.js +9 -0
  184. package/dist/gs/syscall/js/index.js.map +1 -1
  185. package/dist/gs/testing/testing.js +59 -15
  186. package/dist/gs/testing/testing.js.map +1 -1
  187. package/dist/gs/time/time.d.ts +24 -1
  188. package/dist/gs/time/time.js +43 -3
  189. package/dist/gs/time/time.js.map +1 -1
  190. package/dist/gs/unique/index.js +7 -1
  191. package/dist/gs/unique/index.js.map +1 -1
  192. package/go.mod +3 -3
  193. package/go.sum +16 -0
  194. package/gs/builtin/builtin.ts +25 -2
  195. package/gs/builtin/runtime-contract.test.ts +260 -18
  196. package/gs/builtin/slice.ts +51 -4
  197. package/gs/builtin/type.ts +310 -63
  198. package/gs/builtin/varRef.ts +85 -2
  199. package/gs/bytes/buffer.gs.ts +1 -1
  200. package/gs/bytes/reader.gs.ts +1 -1
  201. package/gs/compress/zlib/index.test.ts +159 -1
  202. package/gs/compress/zlib/index.ts +164 -37
  203. package/gs/compress/zlib/meta.json +4 -1
  204. package/gs/compress/zlib/parity.json +51 -0
  205. package/gs/crypto/sha1/index.test.ts +19 -2
  206. package/gs/crypto/sha1/index.ts +3 -6
  207. package/gs/crypto/sha256/index.test.ts +14 -2
  208. package/gs/crypto/sha256/index.ts +3 -6
  209. package/gs/crypto/sha512/index.test.ts +17 -2
  210. package/gs/crypto/sha512/index.ts +3 -6
  211. package/gs/embed/index.test.ts +87 -0
  212. package/gs/embed/index.ts +229 -5
  213. package/gs/encoding/json/index.test.ts +360 -6
  214. package/gs/encoding/json/index.ts +679 -38
  215. package/gs/encoding/json/parity.json +81 -0
  216. package/gs/fmt/fmt.test.ts +41 -3
  217. package/gs/fmt/fmt.ts +40 -17
  218. package/gs/fmt/meta.json +6 -1
  219. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +211 -3
  220. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +857 -1
  221. package/gs/github.com/go-git/go-billy/v6/osfs/index.test.ts +110 -0
  222. package/gs/github.com/go-git/go-billy/v6/osfs/index.ts +280 -0
  223. package/gs/github.com/go-git/go-billy/v6/osfs/meta.json +8 -0
  224. package/gs/github.com/pkg/errors/errors.ts +54 -30
  225. package/gs/go/scanner/index.test.ts +39 -56
  226. package/gs/go/scanner/index.ts +33 -5
  227. package/gs/go/scanner/parity.json +27 -0
  228. package/gs/go/token/index.ts +22 -6
  229. package/gs/hash/index.test.ts +20 -33
  230. package/gs/hash/index.ts +28 -0
  231. package/gs/hash/parity.json +21 -0
  232. package/gs/internal/goarch/index.test.ts +32 -0
  233. package/gs/internal/goarch/index.ts +45 -13
  234. package/gs/internal/goarch/parity.json +144 -0
  235. package/gs/io/fs/fs.ts +26 -14
  236. package/gs/io/fs/readdir.test.ts +38 -0
  237. package/gs/io/fs/readdir.ts +8 -4
  238. package/gs/io/fs/sub.ts +8 -1
  239. package/gs/io/io.test.ts +77 -6
  240. package/gs/io/io.ts +115 -52
  241. package/gs/io/meta.json +7 -1
  242. package/gs/io/parity.json +162 -0
  243. package/gs/math/bits/index.test.ts +14 -1
  244. package/gs/math/bits/index.ts +75 -32
  245. package/gs/math/bits/parity.json +156 -0
  246. package/gs/mime/index.test.ts +90 -0
  247. package/gs/mime/index.ts +369 -6
  248. package/gs/mime/parity.json +36 -0
  249. package/gs/net/http/httptest/index.test.ts +98 -2
  250. package/gs/net/http/httptest/index.ts +101 -6
  251. package/gs/net/http/httptest/parity.json +15 -0
  252. package/gs/net/http/index.test.ts +797 -12
  253. package/gs/net/http/index.ts +1874 -136
  254. package/gs/net/http/meta.json +16 -1
  255. package/gs/net/http/parity.json +193 -0
  256. package/gs/os/dir_unix.gs.ts +1 -1
  257. package/gs/os/error.gs.ts +1 -1
  258. package/gs/os/exec.gs.ts +4 -8
  259. package/gs/os/exec_posix.gs.ts +1 -1
  260. package/gs/os/file_unix_js.test.ts +52 -0
  261. package/gs/os/index.test.ts +9 -0
  262. package/gs/os/index.ts +1 -0
  263. package/gs/os/meta.json +4 -0
  264. package/gs/os/parity.json +9 -0
  265. package/gs/os/proc.gs.ts +18 -5
  266. package/gs/os/proc.test.ts +26 -0
  267. package/gs/os/readdir.test.ts +56 -0
  268. package/gs/os/root_js.gs.ts +1 -1
  269. package/gs/os/types.gs.ts +1 -1
  270. package/gs/os/types_js.gs.ts +170 -9
  271. package/gs/os/types_unix.gs.ts +1 -1
  272. package/gs/path/path.ts +11 -7
  273. package/gs/reflect/deepequal.test.ts +10 -1
  274. package/gs/reflect/field.test.ts +37 -15
  275. package/gs/reflect/function-types.test.ts +518 -22
  276. package/gs/reflect/index.ts +8 -6
  277. package/gs/reflect/map.ts +20 -0
  278. package/gs/reflect/meta.json +6 -4
  279. package/gs/reflect/parity.json +234 -0
  280. package/gs/reflect/sliceat.test.ts +156 -0
  281. package/gs/reflect/structof.test.ts +401 -0
  282. package/gs/reflect/type.ts +1980 -365
  283. package/gs/reflect/typefor.test.ts +540 -10
  284. package/gs/reflect/types.ts +43 -18
  285. package/gs/reflect/value.ts +105 -45
  286. package/gs/reflect/visiblefields.ts +5 -168
  287. package/gs/runtime/parity.json +24 -0
  288. package/gs/runtime/pprof/index.test.ts +29 -7
  289. package/gs/runtime/pprof/index.ts +56 -30
  290. package/gs/runtime/pprof/parity.json +27 -0
  291. package/gs/runtime/runtime.test.ts +3 -1
  292. package/gs/runtime/runtime.ts +4 -3
  293. package/gs/runtime/trace/index.test.ts +5 -3
  294. package/gs/runtime/trace/index.ts +8 -20
  295. package/gs/runtime/trace/parity.json +36 -0
  296. package/gs/strconv/atoi.gs.ts +1 -1
  297. package/gs/strconv/complex.gs.ts +174 -0
  298. package/gs/strconv/complex.test.ts +65 -0
  299. package/gs/strconv/index.ts +1 -0
  300. package/gs/strconv/parity.json +120 -0
  301. package/gs/strings/builder.ts +1 -1
  302. package/gs/strings/meta.json +5 -2
  303. package/gs/strings/parity.json +186 -0
  304. package/gs/strings/reader.test.ts +2 -2
  305. package/gs/strings/reader.ts +11 -7
  306. package/gs/strings/replace.ts +15 -7
  307. package/gs/strings/strings.test.ts +22 -2
  308. package/gs/strings/strings.ts +64 -6
  309. package/gs/sync/atomic/type.gs.ts +9 -9
  310. package/gs/sync/atomic/value.gs.ts +2 -2
  311. package/gs/sync/meta.json +1 -0
  312. package/gs/sync/sync.test.ts +41 -1
  313. package/gs/sync/sync.ts +41 -16
  314. package/gs/syscall/env.ts +29 -14
  315. package/gs/syscall/js/index.test.ts +18 -0
  316. package/gs/syscall/js/index.ts +12 -0
  317. package/gs/testing/testing.test.ts +99 -3
  318. package/gs/testing/testing.ts +95 -24
  319. package/gs/time/parity.json +225 -0
  320. package/gs/time/time.test.ts +20 -2
  321. package/gs/time/time.ts +49 -7
  322. package/gs/unique/index.ts +7 -1
  323. package/package.json +4 -2
  324. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.d.ts +0 -217
  325. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js +0 -814
  326. package/dist/gs/github.com/aperturerobotics/starpc/srpc/index.js.map +0 -1
  327. package/gs/github.com/aperturerobotics/starpc/srpc/index.test.ts +0 -31
  328. package/gs/github.com/aperturerobotics/starpc/srpc/index.ts +0 -1233
  329. package/gs/github.com/aperturerobotics/starpc/srpc/meta.json +0 -46
  330. /package/compiler/{wasm_api.go → wasm-api.go} +0 -0
@@ -4,70 +4,225 @@ import * as context from '@goscript/context/index.js'
4
4
  import * as errors from '@goscript/errors/index.js'
5
5
  import * as fs from '@goscript/io/fs/fs.js'
6
6
  import * as io from '@goscript/io/index.js'
7
+ import * as strings from '@goscript/strings/index.js'
7
8
  import * as time from '@goscript/time/index.js'
8
9
 
10
+ export const StatusContinue = 100
11
+ export const StatusSwitchingProtocols = 101
12
+ export const StatusProcessing = 102
13
+ export const StatusEarlyHints = 103
9
14
  export const StatusOK = 200
10
15
  export const StatusCreated = 201
16
+ export const StatusAccepted = 202
17
+ export const StatusNonAuthoritativeInfo = 203
18
+ export const StatusNoContent = 204
19
+ export const StatusResetContent = 205
11
20
  export const StatusPartialContent = 206
21
+ export const StatusMultiStatus = 207
22
+ export const StatusAlreadyReported = 208
23
+ export const StatusIMUsed = 226
24
+ export const StatusMultipleChoices = 300
12
25
  export const StatusMovedPermanently = 301
26
+ export const StatusFound = 302
27
+ export const StatusSeeOther = 303
28
+ export const StatusNotModified = 304
29
+ export const StatusUseProxy = 305
30
+ export const StatusTemporaryRedirect = 307
31
+ export const StatusPermanentRedirect = 308
13
32
  export const StatusBadRequest = 400
14
33
  export const StatusUnauthorized = 401
34
+ export const StatusPaymentRequired = 402
15
35
  export const StatusForbidden = 403
36
+ export const StatusNotFound = 404
16
37
  export const StatusMethodNotAllowed = 405
38
+ export const StatusNotAcceptable = 406
39
+ export const StatusProxyAuthRequired = 407
17
40
  export const StatusRequestTimeout = 408
18
41
  export const StatusConflict = 409
19
- export const StatusNotFound = 404
42
+ export const StatusGone = 410
43
+ export const StatusLengthRequired = 411
44
+ export const StatusPreconditionFailed = 412
45
+ export const StatusRequestEntityTooLarge = 413
46
+ export const StatusRequestURITooLong = 414
20
47
  export const StatusUnsupportedMediaType = 415
48
+ export const StatusRequestedRangeNotSatisfiable = 416
49
+ export const StatusExpectationFailed = 417
21
50
  export const StatusTeapot = 418
51
+ export const StatusMisdirectedRequest = 421
52
+ export const StatusUnprocessableEntity = 422
53
+ export const StatusLocked = 423
54
+ export const StatusFailedDependency = 424
55
+ export const StatusTooEarly = 425
56
+ export const StatusUpgradeRequired = 426
57
+ export const StatusPreconditionRequired = 428
22
58
  export const StatusTooManyRequests = 429
23
- export const StatusRequestedRangeNotSatisfiable = 416
59
+ export const StatusRequestHeaderFieldsTooLarge = 431
60
+ export const StatusUnavailableForLegalReasons = 451
24
61
  export const StatusInternalServerError = 500
62
+ export const StatusNotImplemented = 501
63
+ export const StatusBadGateway = 502
25
64
  export const StatusServiceUnavailable = 503
65
+ export const StatusGatewayTimeout = 504
66
+ export const StatusHTTPVersionNotSupported = 505
67
+ export const StatusVariantAlsoNegotiates = 506
68
+ export const StatusInsufficientStorage = 507
69
+ export const StatusLoopDetected = 508
70
+ export const StatusNotExtended = 510
71
+ export const StatusNetworkAuthenticationRequired = 511
26
72
 
27
73
  export const MethodGet = 'GET'
74
+ export const MethodHead = 'HEAD'
28
75
  export const MethodPost = 'POST'
76
+ export const MethodPut = 'PUT'
77
+ export const MethodPatch = 'PATCH'
29
78
  export const MethodDelete = 'DELETE'
79
+ export const MethodConnect = 'CONNECT'
80
+ export const MethodOptions = 'OPTIONS'
81
+ export const MethodTrace = 'TRACE'
82
+
83
+ export const DefaultMaxHeaderBytes = 1 << 20
84
+ export const DefaultMaxIdleConnsPerHost = 2
85
+ export const TimeFormat = 'Mon, 02 Jan 2006 15:04:05 GMT'
86
+ export const TrailerPrefix = 'Trailer:'
87
+
88
+ export class ProtocolError {
89
+ public ErrorString: string
90
+
91
+ constructor(errorString: string) {
92
+ this.ErrorString = errorString
93
+ }
94
+
95
+ public Error(): string {
96
+ return this.ErrorString
97
+ }
98
+ }
99
+
100
+ export class MaxBytesError {
101
+ public Limit: number
102
+
103
+ constructor(init?: Partial<MaxBytesError>) {
104
+ this.Limit = init?.Limit ?? 0
105
+ }
106
+
107
+ public Error(): string {
108
+ return 'http: request body too large'
109
+ }
110
+ }
30
111
 
31
- export const ErrNotSupported = errors.New('feature not supported')
112
+ export const ErrNotSupported = new ProtocolError('feature not supported')
113
+ export const ErrUnexpectedTrailer = new ProtocolError('trailer header without chunked transfer encoding')
114
+ export const ErrMissingBoundary = new ProtocolError('no multipart boundary param in Content-Type')
115
+ export const ErrNotMultipart = new ProtocolError("request Content-Type isn't multipart/form-data")
116
+ export const ErrHeaderTooLong = new ProtocolError('header too long')
117
+ export const ErrShortBody = new ProtocolError('entity body too short')
118
+ export const ErrMissingContentLength = new ProtocolError('missing ContentLength in HEAD response')
119
+ export const ErrBodyNotAllowed = errors.New('http: request method or response status code does not allow body')
120
+ export const ErrBodyReadAfterClose = errors.New('http: invalid Read on closed Body')
121
+ export const ErrContentLength = errors.New('http: wrote more than the declared Content-Length')
122
+ export const ErrHandlerTimeout = errors.New('http: Handler timeout')
123
+ export const ErrHijacked = errors.New('http: connection has been hijacked')
124
+ export const ErrLineTooLong = errors.New('header line too long')
125
+ export const ErrMissingFile = errors.New('http: no such file')
126
+ export const ErrNoCookie = errors.New('http: named cookie not present')
127
+ export const ErrNoLocation = errors.New('http: no Location header in response')
128
+ export const ErrSchemeMismatch = errors.New('http: server gave HTTP response to HTTPS client')
129
+ export const ErrServerClosed = errors.New('http: Server closed')
130
+ export const ErrAbortHandler = errors.New('net/http: abort Handler')
131
+ export const ErrSkipAltProtocol = errors.New('net/http: skip alternate protocol')
132
+ export const ErrUseLastResponse = errors.New('net/http: use last response')
133
+ export const ErrWriteAfterFlush = errors.New('unused')
134
+ const errBlankCookie = errors.New('http: blank cookie')
135
+ const errEqualNotFoundInCookie = errors.New("http: '=' not found in cookie")
136
+ const errInvalidCookieName = errors.New('http: invalid cookie name')
137
+ const errInvalidCookieValue = errors.New('http: invalid cookie value')
138
+ const errCookieNumLimitExceeded = errors.New('http: number of cookies exceeded limit')
139
+ const errCrossOriginRequest = errors.New('cross-origin request detected from Sec-Fetch-Site header')
140
+ const errCrossOriginRequestFromOldBrowser = errors.New(
141
+ 'cross-origin request detected, and/or browser is out of date: Sec-Fetch-Site is missing, and Origin does not match Host',
142
+ )
32
143
  export const ServerContextKey = Symbol('net/http ServerContextKey')
144
+ export const LocalAddrContextKey = Symbol('net/http LocalAddrContextKey')
145
+
146
+ export type SameSite = number
147
+ export const SameSiteDefaultMode = 1
148
+ export const SameSiteLaxMode = 2
149
+ export const SameSiteStrictMode = 3
150
+ export const SameSiteNoneMode = 4
151
+
152
+ export type ConnState = number
153
+ export const StateNew = 0
154
+ export const StateActive = 1
155
+ export const StateIdle = 2
156
+ export const StateHijacked = 3
157
+ export const StateClosed = 4
158
+
159
+ const statusTexts = new Map<number, string>([
160
+ [StatusContinue, 'Continue'],
161
+ [StatusSwitchingProtocols, 'Switching Protocols'],
162
+ [StatusProcessing, 'Processing'],
163
+ [StatusEarlyHints, 'Early Hints'],
164
+ [StatusOK, 'OK'],
165
+ [StatusCreated, 'Created'],
166
+ [StatusAccepted, 'Accepted'],
167
+ [StatusNonAuthoritativeInfo, 'Non-Authoritative Information'],
168
+ [StatusNoContent, 'No Content'],
169
+ [StatusResetContent, 'Reset Content'],
170
+ [StatusPartialContent, 'Partial Content'],
171
+ [StatusMultiStatus, 'Multi-Status'],
172
+ [StatusAlreadyReported, 'Already Reported'],
173
+ [StatusIMUsed, 'IM Used'],
174
+ [StatusMultipleChoices, 'Multiple Choices'],
175
+ [StatusMovedPermanently, 'Moved Permanently'],
176
+ [StatusFound, 'Found'],
177
+ [StatusSeeOther, 'See Other'],
178
+ [StatusNotModified, 'Not Modified'],
179
+ [StatusUseProxy, 'Use Proxy'],
180
+ [StatusTemporaryRedirect, 'Temporary Redirect'],
181
+ [StatusPermanentRedirect, 'Permanent Redirect'],
182
+ [StatusBadRequest, 'Bad Request'],
183
+ [StatusUnauthorized, 'Unauthorized'],
184
+ [StatusPaymentRequired, 'Payment Required'],
185
+ [StatusForbidden, 'Forbidden'],
186
+ [StatusNotFound, 'Not Found'],
187
+ [StatusMethodNotAllowed, 'Method Not Allowed'],
188
+ [StatusNotAcceptable, 'Not Acceptable'],
189
+ [StatusProxyAuthRequired, 'Proxy Authentication Required'],
190
+ [StatusRequestTimeout, 'Request Timeout'],
191
+ [StatusConflict, 'Conflict'],
192
+ [StatusGone, 'Gone'],
193
+ [StatusLengthRequired, 'Length Required'],
194
+ [StatusPreconditionFailed, 'Precondition Failed'],
195
+ [StatusRequestEntityTooLarge, 'Request Entity Too Large'],
196
+ [StatusRequestURITooLong, 'Request URI Too Long'],
197
+ [StatusUnsupportedMediaType, 'Unsupported Media Type'],
198
+ [StatusRequestedRangeNotSatisfiable, 'Requested Range Not Satisfiable'],
199
+ [StatusExpectationFailed, 'Expectation Failed'],
200
+ [StatusTeapot, "I'm a teapot"],
201
+ [StatusMisdirectedRequest, 'Misdirected Request'],
202
+ [StatusUnprocessableEntity, 'Unprocessable Entity'],
203
+ [StatusLocked, 'Locked'],
204
+ [StatusFailedDependency, 'Failed Dependency'],
205
+ [StatusTooEarly, 'Too Early'],
206
+ [StatusUpgradeRequired, 'Upgrade Required'],
207
+ [StatusPreconditionRequired, 'Precondition Required'],
208
+ [StatusTooManyRequests, 'Too Many Requests'],
209
+ [StatusRequestHeaderFieldsTooLarge, 'Request Header Fields Too Large'],
210
+ [StatusUnavailableForLegalReasons, 'Unavailable For Legal Reasons'],
211
+ [StatusInternalServerError, 'Internal Server Error'],
212
+ [StatusNotImplemented, 'Not Implemented'],
213
+ [StatusBadGateway, 'Bad Gateway'],
214
+ [StatusServiceUnavailable, 'Service Unavailable'],
215
+ [StatusGatewayTimeout, 'Gateway Timeout'],
216
+ [StatusHTTPVersionNotSupported, 'HTTP Version Not Supported'],
217
+ [StatusVariantAlsoNegotiates, 'Variant Also Negotiates'],
218
+ [StatusInsufficientStorage, 'Insufficient Storage'],
219
+ [StatusLoopDetected, 'Loop Detected'],
220
+ [StatusNotExtended, 'Not Extended'],
221
+ [StatusNetworkAuthenticationRequired, 'Network Authentication Required'],
222
+ ])
33
223
 
34
224
  export function StatusText(code: number): string {
35
- switch (code) {
36
- case StatusOK:
37
- return 'OK'
38
- case StatusMovedPermanently:
39
- return 'Moved Permanently'
40
- case StatusUnauthorized:
41
- return 'Unauthorized'
42
- case StatusForbidden:
43
- return 'Forbidden'
44
- case StatusMethodNotAllowed:
45
- return 'Method Not Allowed'
46
- case StatusBadRequest:
47
- return 'Bad Request'
48
- case StatusRequestTimeout:
49
- return 'Request Timeout'
50
- case StatusConflict:
51
- return 'Conflict'
52
- case StatusNotFound:
53
- return 'Not Found'
54
- case StatusUnsupportedMediaType:
55
- return 'Unsupported Media Type'
56
- case StatusTooManyRequests:
57
- return 'Too Many Requests'
58
- case StatusPartialContent:
59
- return 'Partial Content'
60
- case StatusRequestedRangeNotSatisfiable:
61
- return 'Requested Range Not Satisfiable'
62
- case StatusTeapot:
63
- return "I'm a teapot"
64
- case StatusInternalServerError:
65
- return 'Internal Server Error'
66
- case StatusServiceUnavailable:
67
- return 'Service Unavailable'
68
- default:
69
- return ''
70
- }
225
+ return statusTexts.get(code) ?? ''
71
226
  }
72
227
 
73
228
  export type Header = Map<string, $.Slice<string>>
@@ -76,6 +231,10 @@ export const Header = Map as {
76
231
  new(entries?: Iterable<readonly [string, $.Slice<string>]> | null): Header
77
232
  }
78
233
 
234
+ export function CanonicalHeaderKey(s: string): string {
235
+ return canonicalMIMEHeaderKey(s)
236
+ }
237
+
79
238
  export function Header_Add(h: Header, key: string, value: string): void {
80
239
  key = canonicalMIMEHeaderKey(key)
81
240
  const values = Array.from(h.get(key) ?? [])
@@ -96,6 +255,48 @@ export function Header_Set(h: Header, key: string, value: string): void {
96
255
  h.set(canonicalMIMEHeaderKey(key), $.arrayToSlice([value]))
97
256
  }
98
257
 
258
+ export function Header_Values(h: Header, key: string): $.Slice<string> {
259
+ return h.get(canonicalMIMEHeaderKey(key)) ?? null
260
+ }
261
+
262
+ export function Header_Clone(h: Header): Header {
263
+ const cloned = new Header()
264
+ for (const [key, values] of h.entries()) {
265
+ cloned.set(key, $.arrayToSlice(Array.from(values ?? [])))
266
+ }
267
+ return cloned
268
+ }
269
+
270
+ export function Header_Write(h: Header, w: io.Writer): $.GoError {
271
+ return Header_WriteSubset(h, w, null)
272
+ }
273
+
274
+ export function Header_WriteSubset(h: Header, w: io.Writer, exclude: Map<string, boolean> | null): $.GoError {
275
+ for (const [key, values] of h.entries()) {
276
+ if (exclude?.get(key) === true) {
277
+ continue
278
+ }
279
+ for (const value of Array.from(values ?? [])) {
280
+ const [, err] = w.Write($.stringToBytes(`${key}: ${value}\r\n`))
281
+ if (err != null) {
282
+ return err
283
+ }
284
+ }
285
+ }
286
+ return null
287
+ }
288
+
289
+ export type Dir = string
290
+
291
+ export interface CookieJar {
292
+ SetCookies(u: any, cookies: $.Slice<Cookie | $.VarRef<Cookie> | null>): void
293
+ Cookies(u: any): $.Slice<Cookie | $.VarRef<Cookie> | null>
294
+ }
295
+
296
+ export interface CloseNotifier {
297
+ CloseNotify(): any
298
+ }
299
+
99
300
  function canonicalMIMEHeaderKey(key: string): string {
100
301
  let upper = true
101
302
  let out = ''
@@ -169,18 +370,21 @@ class RequestURL {
169
370
  }
170
371
  }
171
372
 
172
- function parseRequestURL(rawURL: string): RequestURL {
373
+ function parseRequestURL(rawURL: string): [RequestURL | null, $.GoError] {
173
374
  try {
375
+ if (/%(?![0-9A-Fa-f]{2})/.test(rawURL)) {
376
+ return [null, errors.New(`parse "${rawURL}": invalid URL escape`)]
377
+ }
174
378
  const parsed = new URL(rawURL, 'http://goscript.invalid')
175
379
  const hasHost = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(rawURL)
176
- return new RequestURL(
380
+ return [new RequestURL(
177
381
  parsed.pathname,
178
382
  parsed.search.startsWith('?') ? parsed.search.slice(1) : parsed.search,
179
383
  hasHost ? parsed.protocol.replace(/:$/, '') : '',
180
384
  hasHost ? parsed.host : '',
181
- )
385
+ ), null]
182
386
  } catch {
183
- return new RequestURL('', '')
387
+ return [null, errors.New(`parse "${rawURL}": invalid URL`)]
184
388
  }
185
389
  }
186
390
 
@@ -200,6 +404,136 @@ class responseBody implements io.ReadCloser {
200
404
  }
201
405
  }
202
406
 
407
+ class noBody implements io.ReadCloser {
408
+ public Read(_p: $.Bytes): [number, $.GoError] {
409
+ return [0, io.EOF]
410
+ }
411
+
412
+ public Close(): $.GoError {
413
+ return null
414
+ }
415
+
416
+ public clone(): noBody {
417
+ return this
418
+ }
419
+ }
420
+
421
+ export const NoBody: io.ReadCloser = new noBody()
422
+
423
+ export class Cookie {
424
+ public Name: string
425
+ public Value: string
426
+ public Quoted: boolean
427
+ public Path: string
428
+ public Domain: string
429
+ public Expires: time.Time
430
+ public RawExpires: string
431
+ public MaxAge: number
432
+ public Secure: boolean
433
+ public HttpOnly: boolean
434
+ public SameSite: number
435
+ public Partitioned: boolean
436
+ public Raw: string
437
+ public Unparsed: $.Slice<string>
438
+
439
+ constructor(init?: Partial<Cookie>) {
440
+ this.Name = init?.Name ?? ''
441
+ this.Value = init?.Value ?? ''
442
+ this.Quoted = init?.Quoted ?? false
443
+ this.Path = init?.Path ?? ''
444
+ this.Domain = init?.Domain ?? ''
445
+ this.Expires = init?.Expires ?? new time.Time()
446
+ this.RawExpires = init?.RawExpires ?? ''
447
+ this.MaxAge = init?.MaxAge ?? 0
448
+ this.Secure = init?.Secure ?? false
449
+ this.HttpOnly = init?.HttpOnly ?? false
450
+ this.SameSite = init?.SameSite ?? 0
451
+ this.Partitioned = init?.Partitioned ?? false
452
+ this.Raw = init?.Raw ?? ''
453
+ this.Unparsed = init?.Unparsed ?? null
454
+ }
455
+
456
+ public String(): string {
457
+ const parts = [`${this.Name}=${this.Quoted ? quoteCookieValue(this.Value) : this.Value}`]
458
+ if (this.Path !== '') {
459
+ parts.push(`Path=${this.Path}`)
460
+ }
461
+ if (this.Domain !== '') {
462
+ parts.push(`Domain=${this.Domain}`)
463
+ }
464
+ if (this.MaxAge > 0) {
465
+ parts.push(`Max-Age=${this.MaxAge}`)
466
+ } else if (this.MaxAge < 0) {
467
+ parts.push('Max-Age=0')
468
+ }
469
+ if (this.HttpOnly) {
470
+ parts.push('HttpOnly')
471
+ }
472
+ if (this.Secure) {
473
+ parts.push('Secure')
474
+ }
475
+ switch (this.SameSite) {
476
+ case SameSiteLaxMode:
477
+ parts.push('SameSite=Lax')
478
+ break
479
+ case SameSiteStrictMode:
480
+ parts.push('SameSite=Strict')
481
+ break
482
+ case SameSiteNoneMode:
483
+ parts.push('SameSite=None')
484
+ break
485
+ }
486
+ if (this.Partitioned) {
487
+ parts.push('Partitioned')
488
+ }
489
+ return parts.join('; ')
490
+ }
491
+ }
492
+
493
+ function quoteCookieValue(value: string): string {
494
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`
495
+ }
496
+
497
+ function isToken(value: string): boolean {
498
+ return /^[!#$%&'*+\-.^_`{}|~0-9A-Za-z]+$/.test(value)
499
+ }
500
+
501
+ function validCookieValueByte(code: number): boolean {
502
+ return code >= 0x20 && code < 0x7f && code !== 0x22 && code !== 0x3b && code !== 0x5c
503
+ }
504
+
505
+ function parseCookieValue(raw: string, allowDoubleQuote: boolean): [string, boolean, boolean] {
506
+ let value = raw
507
+ let quoted = false
508
+ if (allowDoubleQuote && value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
509
+ value = value.slice(1, -1)
510
+ quoted = true
511
+ }
512
+ for (let i = 0; i < value.length; i++) {
513
+ if (!validCookieValueByte(value.charCodeAt(i))) {
514
+ return ['', quoted, false]
515
+ }
516
+ }
517
+ return [value, quoted, true]
518
+ }
519
+
520
+ function asciiLower(value: string): [string, boolean] {
521
+ for (let i = 0; i < value.length; i++) {
522
+ if (value.charCodeAt(i) > 0x7f) {
523
+ return ['', false]
524
+ }
525
+ }
526
+ return [value.toLowerCase(), true]
527
+ }
528
+
529
+ export function SetCookie(w: ResponseWriter | null, cookie: Cookie | $.VarRef<Cookie> | null): void {
530
+ const c = $.pointerValue<Cookie | null>(cookie)
531
+ if (w == null || c == null) {
532
+ return
533
+ }
534
+ Header_Add(w.Header(), 'Set-Cookie', c.String())
535
+ }
536
+
203
537
  class memoryResponseWriter implements ResponseWriter {
204
538
  public Code = StatusOK
205
539
  public Body = new bytes.Buffer()
@@ -244,12 +578,29 @@ export function RegisterInProcessServer(handler: Handler | null): string {
244
578
  }
245
579
 
246
580
  export function UnregisterInProcessServer(rawURL: string): void {
247
- const parsed = parseRequestURL(rawURL)
248
- if (parsed.Host !== '') {
581
+ const [parsed] = parseRequestURL(rawURL)
582
+ if (parsed != null && parsed.Host !== '') {
249
583
  inProcessServers.delete(parsed.Host)
250
584
  }
251
585
  }
252
586
 
587
+ function inProcessServerRequest(request: Request): Request {
588
+ const req = Object.assign(
589
+ Object.create(Object.getPrototypeOf(request)),
590
+ request,
591
+ ) as Request
592
+ const rawQuery = request.URL?.RawQuery ?? ''
593
+ const query = rawQuery === '' ? '' : `?${rawQuery}`
594
+ req.RequestURI = `${request.URL?.Path ?? '/'}${query}`
595
+ req.Host = request.Host === '' ? request.URL?.Host ?? '' : request.Host
596
+ if (req.URL?.clone != null) {
597
+ req.URL = req.URL.clone()
598
+ req.URL.Scheme = ''
599
+ req.URL.Host = ''
600
+ }
601
+ return req
602
+ }
603
+
253
604
  export interface ResponseWriter {
254
605
  Header(): Header
255
606
  Write(p: $.Slice<number>): [number, $.GoError]
@@ -259,21 +610,49 @@ export interface ResponseWriter {
259
610
  export class Request {
260
611
  public Method: string
261
612
  public URL: any
262
- public Body: io.Reader | null
613
+ public Proto: string
614
+ public ProtoMajor: number
615
+ public ProtoMinor: number
616
+ public Body: io.ReadCloser | null
263
617
  public Header: Header
264
618
  public ContentLength: number
619
+ public TransferEncoding: $.Slice<string>
620
+ public Close: boolean
621
+ public Host: string
622
+ public Form: any
623
+ public PostForm: any
624
+ public MultipartForm: any
625
+ public Trailer: Header
265
626
  public RequestURI: string
266
627
  public RemoteAddr: string
628
+ public TLS: any
629
+ public Cancel: any
630
+ public Response: Response | $.VarRef<Response> | null
631
+ public Pattern: string
267
632
  private ctx: context.Context
268
633
 
269
634
  constructor(init?: Partial<Request> & { ctx?: context.Context }) {
270
635
  this.Method = init?.Method ?? ''
271
636
  this.URL = init?.URL ?? null
637
+ this.Proto = init?.Proto ?? 'HTTP/1.1'
638
+ this.ProtoMajor = init?.ProtoMajor ?? 1
639
+ this.ProtoMinor = init?.ProtoMinor ?? 1
272
640
  this.Body = init?.Body ?? null
273
641
  this.Header = init?.Header ?? new Header()
274
642
  this.ContentLength = init?.ContentLength ?? 0
643
+ this.TransferEncoding = init?.TransferEncoding ?? null
644
+ this.Close = init?.Close ?? false
645
+ this.Host = init?.Host ?? ''
646
+ this.Form = init?.Form ?? null
647
+ this.PostForm = init?.PostForm ?? null
648
+ this.MultipartForm = init?.MultipartForm ?? null
649
+ this.Trailer = init?.Trailer ?? new Header()
275
650
  this.RequestURI = init?.RequestURI ?? ''
276
651
  this.RemoteAddr = init?.RemoteAddr ?? ''
652
+ this.TLS = init?.TLS ?? null
653
+ this.Cancel = init?.Cancel ?? null
654
+ this.Response = init?.Response ?? null
655
+ this.Pattern = init?.Pattern ?? ''
277
656
  this.ctx = (init as { ctx?: context.Context } | undefined)?.ctx ?? context.Background()
278
657
  }
279
658
 
@@ -289,11 +668,25 @@ export class Request {
289
668
  return new Request({
290
669
  Method: this.Method,
291
670
  URL: this.URL?.clone != null ? this.URL.clone() : this.URL == null ? null : { ...this.URL },
671
+ Proto: this.Proto,
672
+ ProtoMajor: this.ProtoMajor,
673
+ ProtoMinor: this.ProtoMinor,
292
674
  Body: this.Body,
293
- Header: this.Header,
675
+ Header: Header_Clone(this.Header),
294
676
  ContentLength: this.ContentLength,
677
+ TransferEncoding: this.TransferEncoding,
678
+ Close: this.Close,
679
+ Host: this.Host,
680
+ Form: this.Form,
681
+ PostForm: this.PostForm,
682
+ MultipartForm: this.MultipartForm,
683
+ Trailer: Header_Clone(this.Trailer),
295
684
  RequestURI: this.RequestURI,
296
685
  RemoteAddr: this.RemoteAddr,
686
+ TLS: this.TLS,
687
+ Cancel: this.Cancel,
688
+ Response: this.Response,
689
+ Pattern: this.Pattern,
297
690
  ctx,
298
691
  })
299
692
  }
@@ -302,6 +695,57 @@ export class Request {
302
695
  return Header_Get(this.Header, 'User-Agent')
303
696
  }
304
697
 
698
+ public Referer(): string {
699
+ return Header_Get(this.Header, 'Referer')
700
+ }
701
+
702
+ public ProtoAtLeast(major: number, minor: number): boolean {
703
+ return this.ProtoMajor > major || (this.ProtoMajor === major && this.ProtoMinor >= minor)
704
+ }
705
+
706
+ public Cookie(name: string): [Cookie | null, $.GoError] {
707
+ for (const cookie of Array.from(this.Cookies() ?? [])) {
708
+ if (cookie?.Name === name) {
709
+ return [cookie, null]
710
+ }
711
+ }
712
+ return [null, ErrNoCookie]
713
+ }
714
+
715
+ public Cookies(): $.Slice<Cookie | null> {
716
+ const raw = Header_Get(this.Header, 'Cookie')
717
+ if (raw === '') {
718
+ return null
719
+ }
720
+ const [cookies] = ParseCookie(raw)
721
+ return cookies
722
+ }
723
+
724
+ public AddCookie(cookie: Cookie | $.VarRef<Cookie> | null): void {
725
+ const c = $.pointerValue<Cookie | null>(cookie)
726
+ if (c != null) {
727
+ Header_Add(this.Header, 'Cookie', c.String())
728
+ }
729
+ }
730
+
731
+ public SetBasicAuth(username: string, password: string): void {
732
+ const encoded = globalThis.btoa(`${username}:${password}`)
733
+ Header_Set(this.Header, 'Authorization', `Basic ${encoded}`)
734
+ }
735
+
736
+ public BasicAuth(): [string, string, boolean] {
737
+ const value = Header_Get(this.Header, 'Authorization')
738
+ if (!value.startsWith('Basic ')) {
739
+ return ['', '', false]
740
+ }
741
+ const decoded = globalThis.atob(value.slice('Basic '.length))
742
+ const idx = decoded.indexOf(':')
743
+ if (idx < 0) {
744
+ return ['', '', false]
745
+ }
746
+ return [decoded.slice(0, idx), decoded.slice(idx + 1), true]
747
+ }
748
+
305
749
  public FormValue(key: string): string {
306
750
  const query = this.URL?.Query
307
751
  return typeof query === 'function' ? query.call(this.URL).Get(key) : ''
@@ -311,14 +755,34 @@ export class Request {
311
755
  export class Response {
312
756
  public Status: string
313
757
  public StatusCode: number
758
+ public Proto: string
759
+ public ProtoMajor: number
760
+ public ProtoMinor: number
314
761
  public Body: io.ReadCloser | null
315
762
  public Header: Header
763
+ public ContentLength: number
764
+ public TransferEncoding: $.Slice<string>
765
+ public Close: boolean
766
+ public Uncompressed: boolean
767
+ public Trailer: Header
768
+ public Request: Request | $.VarRef<Request> | null
769
+ public TLS: any
316
770
 
317
771
  constructor(init?: Partial<Response>) {
318
772
  this.Status = init?.Status ?? ''
319
773
  this.StatusCode = init?.StatusCode ?? 0
774
+ this.Proto = init?.Proto ?? 'HTTP/1.1'
775
+ this.ProtoMajor = init?.ProtoMajor ?? 1
776
+ this.ProtoMinor = init?.ProtoMinor ?? 1
320
777
  this.Body = init?.Body ?? null
321
778
  this.Header = init?.Header ?? new Header()
779
+ this.ContentLength = init?.ContentLength ?? 0
780
+ this.TransferEncoding = init?.TransferEncoding ?? null
781
+ this.Close = init?.Close ?? false
782
+ this.Uncompressed = init?.Uncompressed ?? false
783
+ this.Trailer = init?.Trailer ?? new Header()
784
+ this.Request = init?.Request ?? null
785
+ this.TLS = init?.TLS ?? null
322
786
  if (this.Status === '' && this.StatusCode !== 0) {
323
787
  const text = StatusText(this.StatusCode)
324
788
  this.Status = text === '' ? String(this.StatusCode) : `${this.StatusCode} ${text}`
@@ -331,8 +795,90 @@ export class Response {
331
795
  Header: this.Header,
332
796
  Status: this.Status,
333
797
  StatusCode: this.StatusCode,
798
+ Proto: this.Proto,
799
+ ProtoMajor: this.ProtoMajor,
800
+ ProtoMinor: this.ProtoMinor,
801
+ ContentLength: this.ContentLength,
802
+ TransferEncoding: this.TransferEncoding,
803
+ Close: this.Close,
804
+ Uncompressed: this.Uncompressed,
805
+ Trailer: this.Trailer,
806
+ Request: this.Request,
807
+ TLS: this.TLS,
334
808
  })
335
809
  }
810
+
811
+ public Cookies(): $.Slice<Cookie | null> {
812
+ const values = Header_Values(this.Header, 'Set-Cookie')
813
+ if (values == null) {
814
+ return null
815
+ }
816
+ const cookies: Array<Cookie | null> = []
817
+ for (const value of Array.from(values)) {
818
+ const [cookie] = ParseSetCookie(String(value))
819
+ if (cookie != null) {
820
+ cookies.push(cookie)
821
+ }
822
+ }
823
+ return $.arrayToSlice(cookies)
824
+ }
825
+
826
+ public Location(): [any, $.GoError] {
827
+ const location = Header_Get(this.Header, 'Location')
828
+ if (location === '') {
829
+ return [null, ErrNoLocation]
830
+ }
831
+ try {
832
+ return [new URL(location), null]
833
+ } catch (err) {
834
+ return [null, errors.New(String(err))]
835
+ }
836
+ }
837
+
838
+ public ProtoAtLeast(major: number, minor: number): boolean {
839
+ return this.ProtoMajor > major || (this.ProtoMajor === major && this.ProtoMinor >= minor)
840
+ }
841
+
842
+ public Write(w: io.Writer): $.GoError {
843
+ const write = (data: $.Bytes): $.GoError => {
844
+ const [n, err] = w.Write(data)
845
+ if (err != null) {
846
+ return err
847
+ }
848
+ return n === $.len(data) ? null : io.ErrShortWrite
849
+ }
850
+ let err = write($.stringToBytes(`${this.Proto} ${this.Status}\r\n`))
851
+ if (err != null) {
852
+ return err
853
+ }
854
+ err = Header_Write(this.Header, w)
855
+ if (err != null) {
856
+ return err
857
+ }
858
+ err = write($.stringToBytes('\r\n'))
859
+ if (err != null) {
860
+ return err
861
+ }
862
+ if (this.Body == null) {
863
+ return null
864
+ }
865
+ const buf = $.makeSlice<number>(32 * 1024, undefined, 'byte')
866
+ while (true) {
867
+ const [n, readErr] = this.Body.Read(buf)
868
+ if (n > 0) {
869
+ err = write($.goSlice(buf, 0, n))
870
+ if (err != null) {
871
+ return err
872
+ }
873
+ }
874
+ if (readErr === io.EOF) {
875
+ return null
876
+ }
877
+ if (readErr != null) {
878
+ return readErr
879
+ }
880
+ }
881
+ }
336
882
  }
337
883
 
338
884
  export class Client {
@@ -347,125 +893,767 @@ export class Client {
347
893
  ): Promise<[Response | null, $.GoError]> {
348
894
  return await (this.Transport ?? DefaultTransport).RoundTrip(_req)
349
895
  }
350
- }
351
-
352
- export const DefaultClient = new Client()
353
-
354
- export interface RoundTripper {
355
- RoundTrip(req: Request | $.VarRef<Request> | null): [Response | null, $.GoError] | Promise<[Response | null, $.GoError]>
356
- }
357
896
 
358
- class defaultTransport implements RoundTripper {
359
- public async RoundTrip(req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]> {
360
- const request = $.pointerValue<Request | null>(req)
361
- if (request == null) {
362
- return [null, errors.New('net/http: nil Request')]
897
+ public async Get(url: string): Promise<[Response | null, $.GoError]> {
898
+ const [req, err] = NewRequest(MethodGet, url, null)
899
+ if (err != null) {
900
+ return [null, err]
363
901
  }
364
- const host = request.URL?.Host ?? ''
365
- const handler = host === '' ? null : inProcessServers.get(host)
366
- if (handler == null) {
367
- return [null, errors.New('net/http: Client.Do is not implemented in GoScript')]
902
+ return await this.Do(req)
903
+ }
904
+
905
+ public async Head(url: string): Promise<[Response | null, $.GoError]> {
906
+ const [req, err] = NewRequest(MethodHead, url, null)
907
+ if (err != null) {
908
+ return [null, err]
368
909
  }
369
- const recorder = new memoryResponseWriter()
370
- const served = handler.ServeHTTP(recorder, request)
371
- if (served instanceof Promise) {
372
- await served
910
+ return await this.Do(req)
911
+ }
912
+
913
+ public async Post(url: string, contentType: string, body: io.Reader | null): Promise<[Response | null, $.GoError]> {
914
+ const [req, err] = NewRequest(MethodPost, url, body)
915
+ if (err != null || req == null) {
916
+ return [null, err]
373
917
  }
374
- return [recorder.Result(), null]
918
+ Header_Set(req.Header, 'Content-Type', contentType)
919
+ return await this.Do(req)
375
920
  }
376
- }
377
921
 
378
- export const DefaultTransport: RoundTripper = new defaultTransport()
922
+ public async PostForm(url: string, data: any): Promise<[Response | null, $.GoError]> {
923
+ return await this.Post(url, 'application/x-www-form-urlencoded', bytes.NewReader($.stringToBytes(encodeFormData(data))))
924
+ }
379
925
 
380
- export interface FileSystem {
381
- Open(name: string): [File | null, $.GoError]
926
+ public CloseIdleConnections(): void {
927
+ const closer = this.Transport as { CloseIdleConnections?: () => void } | null
928
+ closer?.CloseIdleConnections?.()
929
+ }
382
930
  }
383
931
 
384
- export interface File extends io.Closer, io.Reader, io.Seeker {
385
- Readdir(count: number): [$.Slice<fs.FileInfo>, $.GoError]
386
- Stat(): [fs.FileInfo, $.GoError]
932
+ export const DefaultClient = new Client()
933
+
934
+ export class ClientConn {}
935
+
936
+ function encodeFormData(data: any): string {
937
+ if (data == null) {
938
+ return ''
939
+ }
940
+ if (typeof data.Encode === 'function') {
941
+ return String(data.Encode())
942
+ }
943
+ if (data instanceof URLSearchParams) {
944
+ return data.toString()
945
+ }
946
+ const entries =
947
+ data instanceof Map ?
948
+ Array.from(data.entries())
949
+ : typeof data === 'object' ?
950
+ Object.entries(data)
951
+ : []
952
+ entries.sort(([a], [b]) => String(a).localeCompare(String(b)))
953
+ const params = new URLSearchParams()
954
+ for (const [key, value] of entries) {
955
+ appendFormValue(params, String(key), value)
956
+ }
957
+ return params.toString()
387
958
  }
388
959
 
389
- export interface Handler {
390
- ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void>
960
+ function appendFormValue(params: URLSearchParams, key: string, value: unknown): void {
961
+ const unwrapped = unwrapFormValue(value)
962
+ if (unwrapped == null) {
963
+ return
964
+ }
965
+ if (Array.isArray(unwrapped)) {
966
+ for (const item of unwrapped) {
967
+ appendFormValue(params, key, item)
968
+ }
969
+ return
970
+ }
971
+ params.append(key, String(unwrapped))
391
972
  }
392
973
 
393
- export type HandlerFunc = (
394
- w: ResponseWriter | null,
395
- r: Request | $.VarRef<Request> | null,
396
- ) => void | Promise<void>
974
+ function unwrapFormValue(value: unknown): unknown {
975
+ if ($.isVarRef(value)) {
976
+ return value.value
977
+ }
978
+ if (typeof value === 'object' && value !== null && '__goValue' in value) {
979
+ return (value as { __goValue: unknown }).__goValue
980
+ }
981
+ return value
982
+ }
397
983
 
398
- export function HandlerFunc_ServeHTTP(
399
- h: HandlerFunc,
400
- w: ResponseWriter | null,
401
- r: Request | $.VarRef<Request> | null,
402
- ): void | Promise<void> {
403
- return h(w, r)
984
+ export interface RoundTripper {
985
+ RoundTrip(req: Request | $.VarRef<Request> | null): [Response | null, $.GoError] | Promise<[Response | null, $.GoError]>
404
986
  }
405
987
 
406
- export class Server {
407
- public Addr: string
408
- public BaseContext: ((listener: any) => context.Context) | null
409
- public Handler: Handler | null
410
- public ReadHeaderTimeout: number
411
- public WriteTimeout: number
988
+ export class Protocols {
989
+ private bits = 0
412
990
 
413
- constructor(init?: Partial<Server>) {
414
- this.Addr = init?.Addr ?? ''
415
- this.BaseContext = init?.BaseContext ?? null
416
- this.Handler = init?.Handler ?? null
417
- this.ReadHeaderTimeout = init?.ReadHeaderTimeout ?? 0
418
- this.WriteTimeout = init?.WriteTimeout ?? 0
991
+ public HTTP1(): boolean {
992
+ return (this.bits & 1) !== 0
419
993
  }
420
994
 
421
- public ListenAndServe(): $.GoError {
422
- return errors.New('net/http: Server.ListenAndServe is not implemented in GoScript')
995
+ public SetHTTP1(ok: boolean): void {
996
+ this.setBit(1, ok)
423
997
  }
424
998
 
425
- public ListenAndServeTLS(_certFile: string, _keyFile: string): $.GoError {
426
- return errors.New('net/http: Server.ListenAndServeTLS is not implemented in GoScript')
999
+ public HTTP2(): boolean {
1000
+ return (this.bits & 2) !== 0
427
1001
  }
428
1002
 
429
- public Close(): $.GoError {
430
- return null
1003
+ public SetHTTP2(ok: boolean): void {
1004
+ this.setBit(2, ok)
431
1005
  }
432
1006
 
433
- public Shutdown(_ctx: context.Context): $.GoError {
434
- return null
1007
+ public UnencryptedHTTP2(): boolean {
1008
+ return (this.bits & 4) !== 0
435
1009
  }
436
- }
437
1010
 
438
- export class PushOptions {
439
- public Header: Header
1011
+ public SetUnencryptedHTTP2(ok: boolean): void {
1012
+ this.setBit(4, ok)
1013
+ }
440
1014
 
441
- constructor(init?: Partial<PushOptions>) {
442
- this.Header = init?.Header ?? new Header()
1015
+ public String(): string {
1016
+ const names: string[] = []
1017
+ if (this.HTTP1()) {
1018
+ names.push('HTTP1')
1019
+ }
1020
+ if (this.HTTP2()) {
1021
+ names.push('HTTP2')
1022
+ }
1023
+ if (this.UnencryptedHTTP2()) {
1024
+ names.push('UnencryptedHTTP2')
1025
+ }
1026
+ return `{${names.join(',')}}`
443
1027
  }
444
- }
445
1028
 
446
- export interface Flusher {
447
- Flush(): void
1029
+ private setBit(bit: number, ok: boolean): void {
1030
+ this.bits = ok ? this.bits | bit : this.bits & ~bit
1031
+ }
448
1032
  }
449
1033
 
450
- export interface Hijacker {
451
- Hijack(): [any, any, $.GoError]
1034
+ export class HTTP2Config {
1035
+ public MaxConcurrentStreams = 0
1036
+ public StrictMaxConcurrentRequests = false
1037
+ public MaxDecoderHeaderTableSize = 0
1038
+ public MaxEncoderHeaderTableSize = 0
1039
+ public MaxReadFrameSize = 0
1040
+ public MaxReceiveBufferPerConnection = 0
1041
+ public MaxReceiveBufferPerStream = 0
1042
+ public SendPingTimeout = 0
1043
+ public PingTimeout = 0
1044
+ public WriteByteTimeout = 0
1045
+ public PermitProhibitedCipherSuites = false
1046
+ public CountError: ((errType: string) => void) | null = null
1047
+
1048
+ constructor(init?: Partial<HTTP2Config>) {
1049
+ Object.assign(this, init)
1050
+ }
452
1051
  }
453
1052
 
454
- export interface Pusher {
455
- Push(target: string, opts: PushOptions | $.VarRef<PushOptions> | null): $.GoError
456
- }
1053
+ export class Transport implements RoundTripper {
1054
+ public Proxy: ((req: Request | $.VarRef<Request> | null) => [any, $.GoError]) | null = null
1055
+ public OnProxyConnectResponse: ((ctx: context.Context, proxyURL: any, connectReq: Request, connectRes: Response) => $.GoError) | null = null
1056
+ public DialContext: any = null
1057
+ public Dial: any = null
1058
+ public DialTLSContext: any = null
1059
+ public DialTLS: any = null
1060
+ public TLSClientConfig: any = null
1061
+ public TLSHandshakeTimeout = 0
1062
+ public DisableKeepAlives = false
1063
+ public DisableCompression = false
1064
+ public MaxIdleConns = 0
1065
+ public MaxIdleConnsPerHost = 0
1066
+ public MaxConnsPerHost = 0
1067
+ public IdleConnTimeout = 0
1068
+ public ResponseHeaderTimeout = 0
1069
+ public ExpectContinueTimeout = 0
1070
+ public TLSNextProto: Map<string, any> | null = null
1071
+ public ProxyConnectHeader = new Header()
1072
+ public GetProxyConnectHeader: ((ctx: context.Context, proxyURL: any, target: string) => [Header | null, $.GoError]) | null = null
1073
+ public MaxResponseHeaderBytes = 0
1074
+ public WriteBufferSize = 0
1075
+ public ReadBufferSize = 0
1076
+ public ForceAttemptHTTP2 = false
1077
+ public HTTP2: HTTP2Config | null = null
1078
+ public Protocols: Protocols | null = null
1079
+
1080
+ constructor(init?: Partial<Transport>) {
1081
+ Object.assign(this, init)
1082
+ }
457
1083
 
458
- export class ResponseController {
459
- public rw: ResponseWriter | null
1084
+ public async RoundTrip(req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]> {
1085
+ const request = $.pointerValue<Request | null>(req)
1086
+ if (request == null) {
1087
+ return [null, errors.New('net/http: nil Request')]
1088
+ }
1089
+ const host = request.URL?.Host ?? ''
1090
+ const handler = host === '' ? null : inProcessServers.get(host)
1091
+ if (handler == null) {
1092
+ return await fetchRoundTrip(request)
1093
+ }
1094
+ const recorder = new memoryResponseWriter()
1095
+ let closeErr: $.GoError | undefined
1096
+ try {
1097
+ const served = handler.ServeHTTP(recorder, inProcessServerRequest(request))
1098
+ if (served instanceof Promise) {
1099
+ await served
1100
+ }
1101
+ } finally {
1102
+ closeErr = request.Body?.Close?.() ?? null
1103
+ }
1104
+ if (closeErr != null) {
1105
+ return [null, closeErr]
1106
+ }
1107
+ const response = recorder.Result()
1108
+ if (request.Method === MethodHead) {
1109
+ response.Body = NoBody
1110
+ }
1111
+ return [response, null]
1112
+ }
460
1113
 
461
- constructor(rw: ResponseWriter | null) {
462
- this.rw = rw
1114
+ public CloseIdleConnections(): void {}
1115
+
1116
+ public CancelRequest(_req: Request | $.VarRef<Request> | null): void {}
1117
+
1118
+ public RegisterProtocol(_scheme: string, _rt: RoundTripper): void {}
1119
+
1120
+ public NewClientConn(_ctx: context.Context, _scheme: string, _address: string): [ClientConn | null, $.GoError] {
1121
+ return [null, ErrNotSupported]
463
1122
  }
464
1123
 
465
- public Flush(): $.GoError {
466
- const flusher = this.rw as (Flusher & ResponseWriter) | null
467
- flusher?.Flush?.()
468
- return null
1124
+ public Clone(): Transport {
1125
+ return new Transport(this)
1126
+ }
1127
+ }
1128
+
1129
+ export const DefaultTransport: RoundTripper = new Transport()
1130
+
1131
+ class fileTransport implements RoundTripper {
1132
+ constructor(private root: FileSystem | null) {}
1133
+
1134
+ public async RoundTrip(req: Request | $.VarRef<Request> | null): Promise<[Response | null, $.GoError]> {
1135
+ const request = $.pointerValue<Request | null>(req)
1136
+ const recorder = new memoryResponseWriter()
1137
+ let closeErr: $.GoError | undefined
1138
+ try {
1139
+ await FileServer(this.root).ServeHTTP(recorder, request)
1140
+ } finally {
1141
+ closeErr = request?.Body?.Close?.() ?? null
1142
+ }
1143
+ if (closeErr != null) {
1144
+ return [null, closeErr]
1145
+ }
1146
+ return [recorder.Result(), null]
1147
+ }
1148
+ }
1149
+
1150
+ export function NewFileTransport(root: FileSystem | null): RoundTripper {
1151
+ return new fileTransport(root)
1152
+ }
1153
+
1154
+ export function NewFileTransportFS(fsys: fs.FS): RoundTripper {
1155
+ return NewFileTransport(FS(fsys))
1156
+ }
1157
+
1158
+ async function fetchRoundTrip(request: Request): Promise<[Response | null, $.GoError]> {
1159
+ const requestBody = request.Body
1160
+ const closeRequestBody = (): $.GoError => {
1161
+ if (requestBody == null) {
1162
+ return null
1163
+ }
1164
+ return requestBody.Close()
1165
+ }
1166
+ if (typeof globalThis.fetch !== 'function') {
1167
+ closeRequestBody()
1168
+ return [null, errors.New('net/http: Client.Do is not implemented in GoScript')]
1169
+ }
1170
+ const ctxErr = request.Context()?.Err?.()
1171
+ if (ctxErr != null) {
1172
+ closeRequestBody()
1173
+ return [null, ctxErr]
1174
+ }
1175
+ const headers = new globalThis.Headers()
1176
+ for (const [key, values] of request.Header.entries()) {
1177
+ for (const value of Array.from(values ?? [])) {
1178
+ headers.append(key, String(value))
1179
+ }
1180
+ }
1181
+ let body: Uint8Array | undefined
1182
+ if (requestBody != null && request.Method !== MethodGet && request.Method !== MethodHead) {
1183
+ const [data, err] = await io.ReadAll(requestBody)
1184
+ const closeErr = closeRequestBody()
1185
+ if (err != null) {
1186
+ return [null, err]
1187
+ }
1188
+ if (closeErr != null) {
1189
+ return [null, closeErr]
1190
+ }
1191
+ body = Uint8Array.from(data ?? [])
1192
+ } else {
1193
+ const closeErr = closeRequestBody()
1194
+ if (closeErr != null) {
1195
+ return [null, closeErr]
1196
+ }
1197
+ }
1198
+ try {
1199
+ const bodyInit = body == null ? undefined : Uint8Array.from(body).buffer
1200
+ const fetched = await globalThis.fetch(request.URL?.String?.() ?? '', {
1201
+ method: request.Method || MethodGet,
1202
+ headers,
1203
+ body: bodyInit,
1204
+ })
1205
+ const data = new Uint8Array(await fetched.arrayBuffer())
1206
+ const respHeader = new Header()
1207
+ fetched.headers.forEach((value, key) => Header_Add(respHeader, key, value))
1208
+ return [
1209
+ new Response({
1210
+ Status: `${fetched.status} ${fetched.statusText}`,
1211
+ StatusCode: fetched.status,
1212
+ Body: new responseBody(data),
1213
+ Header: respHeader,
1214
+ ContentLength: Number(fetched.headers.get('content-length') ?? -1),
1215
+ Request: request,
1216
+ }),
1217
+ null,
1218
+ ]
1219
+ } catch (err) {
1220
+ const message = typeof err === 'object' && err != null && 'message' in err
1221
+ ? String((err as { message: unknown }).message)
1222
+ : String(err)
1223
+ return [null, errors.New(message)]
1224
+ }
1225
+ }
1226
+
1227
+ export interface FileSystem {
1228
+ Open(name: string): [File | null, $.GoError]
1229
+ }
1230
+
1231
+ export interface File extends io.Closer, io.Reader, io.Seeker {
1232
+ Readdir(count: number): [$.Slice<fs.FileInfo>, $.GoError]
1233
+ Stat(): [fs.FileInfo, $.GoError]
1234
+ }
1235
+
1236
+ export function FS(fsys: fs.FS): FileSystem {
1237
+ return {
1238
+ Open(name: string): [File | null, $.GoError] {
1239
+ const cleaned = cleanFileServerPath(name)
1240
+ const [file, err] = fsys?.Open(cleaned) ?? [null, fs.ErrInvalid]
1241
+ if (err != null || file == null) {
1242
+ return [null, err]
1243
+ }
1244
+ return [httpFileFromFSFile(file), null]
1245
+ },
1246
+ }
1247
+ }
1248
+
1249
+ function httpFileFromFSFile(file: Exclude<fs.File, null>): File {
1250
+ const seek = (file as Partial<io.Seeker>).Seek
1251
+ const readdir = (file as { Readdir?: (count: number) => [$.Slice<fs.FileInfo>, $.GoError] }).Readdir
1252
+ return {
1253
+ Read: (p) => file.Read(p instanceof Uint8Array ? p : Uint8Array.from(p ?? [])),
1254
+ Close: () => file.Close(),
1255
+ Stat: () => file.Stat(),
1256
+ Seek: seek == null ? () => [0, errors.New('net/http: file does not support seek')] : seek.bind(file),
1257
+ Readdir: readdir == null ? () => [null, io.EOF] : readdir.bind(file),
1258
+ }
1259
+ }
1260
+
1261
+ export function FileServer(root: FileSystem | null): Handler {
1262
+ return {
1263
+ async ServeHTTP(w, r): Promise<void> {
1264
+ const req = $.pointerValue<Request | null>(r)
1265
+ if (w == null || req == null) {
1266
+ return
1267
+ }
1268
+ if (req.Method !== MethodGet && req.Method !== MethodHead) {
1269
+ Error(w, 'method not allowed', StatusMethodNotAllowed)
1270
+ return
1271
+ }
1272
+ const [file, err] = root?.Open(cleanFileServerPath(req.URL?.Path ?? '')) ?? [null, fs.ErrInvalid]
1273
+ if (err != null || file == null) {
1274
+ NotFound(w, req)
1275
+ return
1276
+ }
1277
+ try {
1278
+ const [info, statErr] = file.Stat()
1279
+ if (statErr != null) {
1280
+ Error(w, statErr.Error(), StatusInternalServerError)
1281
+ return
1282
+ }
1283
+ if (info?.IsDir?.() === true) {
1284
+ NotFound(w, req)
1285
+ return
1286
+ }
1287
+ const [data, readErr] = await io.ReadAll(file)
1288
+ if (readErr != null) {
1289
+ Error(w, readErr.Error(), StatusInternalServerError)
1290
+ return
1291
+ }
1292
+ if (info?.Size != null) {
1293
+ Header_Set(w.Header(), 'Content-Length', String(info.Size()))
1294
+ }
1295
+ w.WriteHeader(StatusOK)
1296
+ if (req.Method !== MethodHead) {
1297
+ w.Write(data)
1298
+ }
1299
+ } finally {
1300
+ file.Close()
1301
+ }
1302
+ },
1303
+ }
1304
+ }
1305
+
1306
+ export function FileServerFS(fsys: fs.FS): Handler {
1307
+ return FileServer(FS(fsys))
1308
+ }
1309
+
1310
+ export function ServeFile(
1311
+ w: ResponseWriter | null,
1312
+ r: Request | $.VarRef<Request> | null,
1313
+ _name: string,
1314
+ ): void {
1315
+ const req = $.pointerValue<Request | null>(r)
1316
+ if (w == null || req == null) {
1317
+ return
1318
+ }
1319
+ if (req.Method !== MethodGet && req.Method !== MethodHead) {
1320
+ Error(w, 'method not allowed', StatusMethodNotAllowed)
1321
+ return
1322
+ }
1323
+ NotFound(w, req)
1324
+ }
1325
+
1326
+ export function ServeFileFS(
1327
+ w: ResponseWriter | null,
1328
+ r: Request | $.VarRef<Request> | null,
1329
+ _fsys: fs.FS,
1330
+ name: string,
1331
+ ): void {
1332
+ ServeFile(w, r, name)
1333
+ }
1334
+
1335
+ function cleanFileServerPath(name: string): string {
1336
+ const parts: string[] = []
1337
+ for (const part of name.split('?')[0].split('/')) {
1338
+ if (part === '' || part === '.') {
1339
+ continue
1340
+ }
1341
+ if (part === '..') {
1342
+ parts.pop()
1343
+ continue
1344
+ }
1345
+ parts.push(part)
1346
+ }
1347
+ return parts.length === 0 ? '.' : parts.join('/')
1348
+ }
1349
+
1350
+ export interface Handler {
1351
+ ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void>
1352
+ }
1353
+
1354
+ export type HandlerFunc = (
1355
+ w: ResponseWriter | null,
1356
+ r: Request | $.VarRef<Request> | null,
1357
+ ) => void | Promise<void>
1358
+
1359
+ export function HandlerFunc_ServeHTTP(
1360
+ h: HandlerFunc,
1361
+ w: ResponseWriter | null,
1362
+ r: Request | $.VarRef<Request> | null,
1363
+ ): void | Promise<void> {
1364
+ return h(w, r)
1365
+ }
1366
+
1367
+ export class CrossOriginProtection {
1368
+ private denyHandler: Handler | null = null
1369
+ private trustedOrigins = new Set<string>()
1370
+ private bypassPatterns: string[] = []
1371
+
1372
+ public AddInsecureBypassPattern(pattern: string): void {
1373
+ this.bypassPatterns.push(pattern)
1374
+ }
1375
+
1376
+ public AddTrustedOrigin(origin: string): $.GoError {
1377
+ if (!/^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#]+$/.test(origin)) {
1378
+ return $.newError(`invalid origin "${origin}"`)
1379
+ }
1380
+ try {
1381
+ const parsed = new URL(origin)
1382
+ if (parsed.protocol === '' || parsed.host === '') {
1383
+ return $.newError(`invalid origin "${origin}"`)
1384
+ }
1385
+ } catch (err) {
1386
+ return $.newError(`invalid origin "${origin}": ${String(err)}`)
1387
+ }
1388
+ this.trustedOrigins.add(origin)
1389
+ return null
1390
+ }
1391
+
1392
+ public Check(r: Request | $.VarRef<Request> | null): $.GoError {
1393
+ const req = $.pointerValue<Request | null>(r)
1394
+ if (req == null) {
1395
+ return errCrossOriginRequest
1396
+ }
1397
+ switch (req.Method) {
1398
+ case MethodGet:
1399
+ case MethodHead:
1400
+ case MethodOptions:
1401
+ return null
1402
+ }
1403
+
1404
+ switch (Header_Get(req.Header, 'Sec-Fetch-Site')) {
1405
+ case '':
1406
+ break
1407
+ case 'same-origin':
1408
+ case 'none':
1409
+ return null
1410
+ default:
1411
+ if (this.isRequestExempt(req)) {
1412
+ return null
1413
+ }
1414
+ return errCrossOriginRequest
1415
+ }
1416
+
1417
+ const origin = Header_Get(req.Header, 'Origin')
1418
+ if (origin === '') {
1419
+ return null
1420
+ }
1421
+ if (originHost(origin) === req.Host) {
1422
+ return null
1423
+ }
1424
+ if (this.isRequestExempt(req)) {
1425
+ return null
1426
+ }
1427
+ return errCrossOriginRequestFromOldBrowser
1428
+ }
1429
+
1430
+ public Handler(handler: Handler | null): Handler {
1431
+ return {
1432
+ ServeHTTP: (w, r) => {
1433
+ const err = this.Check(r)
1434
+ if (err != null) {
1435
+ const deny = this.denyHandler
1436
+ if (deny != null) {
1437
+ return deny.ServeHTTP(w, r)
1438
+ }
1439
+ Error(w, err.Error(), StatusForbidden)
1440
+ return
1441
+ }
1442
+ return handler?.ServeHTTP(w, r)
1443
+ },
1444
+ }
1445
+ }
1446
+
1447
+ public SetDenyHandler(handler: Handler | null): void {
1448
+ this.denyHandler = handler
1449
+ }
1450
+
1451
+ private isRequestExempt(req: Request): boolean {
1452
+ for (const pattern of this.bypassPatterns) {
1453
+ if (bypassPatternMatches(pattern, req)) {
1454
+ return true
1455
+ }
1456
+ }
1457
+ const origin = Header_Get(req.Header, 'Origin')
1458
+ return origin !== '' && this.trustedOrigins.has(origin)
1459
+ }
1460
+ }
1461
+
1462
+ export function NewCrossOriginProtection(): CrossOriginProtection {
1463
+ return new CrossOriginProtection()
1464
+ }
1465
+
1466
+ function originHost(origin: string): string {
1467
+ try {
1468
+ return new URL(origin).host
1469
+ } catch {
1470
+ return ''
1471
+ }
1472
+ }
1473
+
1474
+ function bypassPatternMatches(pattern: string, req: Request): boolean {
1475
+ let method = ''
1476
+ let pathPattern = pattern
1477
+ const space = pattern.indexOf(' ')
1478
+ if (space > 0) {
1479
+ method = pattern.slice(0, space)
1480
+ pathPattern = pattern.slice(space + 1)
1481
+ }
1482
+ if (method !== '' && method !== req.Method) {
1483
+ return false
1484
+ }
1485
+ const path = req.URL?.Path ?? ''
1486
+ if (pathPattern.includes('{')) {
1487
+ return wildcardPatternMatches(pathPattern, path)
1488
+ }
1489
+ if (pathPattern.endsWith('/')) {
1490
+ return path === pathPattern || path.startsWith(pathPattern)
1491
+ }
1492
+ return path === pathPattern
1493
+ }
1494
+
1495
+ function wildcardPatternMatches(pattern: string, path: string): boolean {
1496
+ const patternParts = pattern.split('/').filter((part) => part !== '')
1497
+ const pathParts = path.split('/').filter((part) => part !== '')
1498
+ if (patternParts.length !== pathParts.length) {
1499
+ return false
1500
+ }
1501
+ for (let i = 0; i < patternParts.length; i++) {
1502
+ if (/^\{[^}]+\}$/.test(patternParts[i])) {
1503
+ continue
1504
+ }
1505
+ if (patternParts[i] !== pathParts[i]) {
1506
+ return false
1507
+ }
1508
+ }
1509
+ return true
1510
+ }
1511
+
1512
+ export class Server {
1513
+ public Addr: string
1514
+ public BaseContext: ((listener: any) => context.Context) | null
1515
+ public ConnContext: ((ctx: context.Context, conn: any) => context.Context) | null
1516
+ public Handler: Handler | null
1517
+ public DisableGeneralOptionsHandler: boolean
1518
+ public TLSConfig: any
1519
+ public ReadTimeout: number
1520
+ public ReadTimeoutHandler: any
1521
+ public ReadHeaderTimeout: number
1522
+ public WriteTimeout: number
1523
+ public IdleTimeout: number
1524
+ public MaxHeaderBytes: number
1525
+ public TLSNextProto: Map<string, any> | null
1526
+ public ConnState: ((conn: any, state: ConnState) => void) | null
1527
+ public ErrorLog: any
1528
+ public HTTP2: HTTP2Config | null
1529
+ public Protocols: Protocols | null
1530
+
1531
+ constructor(init?: Partial<Server>) {
1532
+ this.Addr = init?.Addr ?? ''
1533
+ this.BaseContext = init?.BaseContext ?? null
1534
+ this.ConnContext = init?.ConnContext ?? null
1535
+ this.Handler = init?.Handler ?? null
1536
+ this.DisableGeneralOptionsHandler = init?.DisableGeneralOptionsHandler ?? false
1537
+ this.TLSConfig = init?.TLSConfig ?? null
1538
+ this.ReadTimeout = init?.ReadTimeout ?? 0
1539
+ this.ReadTimeoutHandler = (init as any)?.ReadTimeoutHandler ?? null
1540
+ this.ReadHeaderTimeout = init?.ReadHeaderTimeout ?? 0
1541
+ this.WriteTimeout = init?.WriteTimeout ?? 0
1542
+ this.IdleTimeout = init?.IdleTimeout ?? 0
1543
+ this.MaxHeaderBytes = init?.MaxHeaderBytes ?? 0
1544
+ this.TLSNextProto = init?.TLSNextProto ?? null
1545
+ this.ConnState = init?.ConnState ?? null
1546
+ this.ErrorLog = init?.ErrorLog ?? null
1547
+ this.HTTP2 = init?.HTTP2 ?? null
1548
+ this.Protocols = init?.Protocols ?? null
1549
+ }
1550
+
1551
+ public ListenAndServe(): $.GoError {
1552
+ return errors.New('net/http: Server.ListenAndServe is not implemented in GoScript')
1553
+ }
1554
+
1555
+ public ListenAndServeTLS(_certFile: string, _keyFile: string): $.GoError {
1556
+ return errors.New('net/http: Server.ListenAndServeTLS is not implemented in GoScript')
1557
+ }
1558
+
1559
+ public Close(): $.GoError {
1560
+ return null
1561
+ }
1562
+
1563
+ public Shutdown(_ctx: context.Context): $.GoError {
1564
+ return null
1565
+ }
1566
+
1567
+ public Serve(_listener: any): $.GoError {
1568
+ return ErrNotSupported
1569
+ }
1570
+
1571
+ public ServeTLS(_listener: any, _certFile: string, _keyFile: string): $.GoError {
1572
+ return ErrNotSupported
1573
+ }
1574
+
1575
+ public ServeHTTP(w: ResponseWriter | null, r: Request | $.VarRef<Request> | null): void | Promise<void> {
1576
+ return (this.Handler ?? DefaultServeMux).ServeHTTP(w, r)
1577
+ }
1578
+
1579
+ public RegisterOnShutdown(_f: () => void): void {}
1580
+
1581
+ public SetKeepAlivesEnabled(_v: boolean): void {}
1582
+ }
1583
+
1584
+ export function ListenAndServe(_addr: string, _handler: Handler | null): $.GoError {
1585
+ return ErrNotSupported
1586
+ }
1587
+
1588
+ export function ListenAndServeTLS(
1589
+ _addr: string,
1590
+ _certFile: string,
1591
+ _keyFile: string,
1592
+ _handler: Handler | null,
1593
+ ): $.GoError {
1594
+ return ErrNotSupported
1595
+ }
1596
+
1597
+ export function Serve(_listener: any, _handler: Handler | null): $.GoError {
1598
+ return ErrNotSupported
1599
+ }
1600
+
1601
+ export function ServeTLS(
1602
+ _listener: any,
1603
+ _handler: Handler | null,
1604
+ _certFile: string,
1605
+ _keyFile: string,
1606
+ ): $.GoError {
1607
+ return ErrNotSupported
1608
+ }
1609
+
1610
+ export class PushOptions {
1611
+ public Header: Header
1612
+
1613
+ constructor(init?: Partial<PushOptions>) {
1614
+ this.Header = init?.Header ?? new Header()
1615
+ }
1616
+ }
1617
+
1618
+ export interface Flusher {
1619
+ Flush(): void
1620
+ }
1621
+
1622
+ export interface Hijacker {
1623
+ Hijack(): [any, any, $.GoError]
1624
+ }
1625
+
1626
+ export interface Pusher {
1627
+ Push(target: string, opts: PushOptions | $.VarRef<PushOptions> | null): $.GoError
1628
+ }
1629
+
1630
+ export class ResponseController {
1631
+ public rw: ResponseWriter | null
1632
+
1633
+ constructor(rw: ResponseWriter | null) {
1634
+ this.rw = rw
1635
+ }
1636
+
1637
+ public Flush(): $.GoError {
1638
+ const flusher = this.rw as (Flusher & ResponseWriter) | null
1639
+ flusher?.Flush?.()
1640
+ return null
1641
+ }
1642
+
1643
+ public Hijack(): [any, any, $.GoError] {
1644
+ return [null, null, ErrNotSupported]
1645
+ }
1646
+
1647
+ public SetReadDeadline(_deadline: time.Time): $.GoError {
1648
+ return ErrNotSupported
1649
+ }
1650
+
1651
+ public SetWriteDeadline(_deadline: time.Time): $.GoError {
1652
+ return ErrNotSupported
1653
+ }
1654
+
1655
+ public EnableFullDuplex(): $.GoError {
1656
+ return ErrNotSupported
469
1657
  }
470
1658
  }
471
1659
 
@@ -473,6 +1661,51 @@ export function NewResponseController(rw: ResponseWriter | null): ResponseContro
473
1661
  return new ResponseController(rw)
474
1662
  }
475
1663
 
1664
+ class maxBytesReader implements io.ReadCloser {
1665
+ private initialLimit: number
1666
+ private remaining: number
1667
+ private err: $.GoError = null
1668
+
1669
+ constructor(private reader: io.ReadCloser, limit: number) {
1670
+ this.initialLimit = Math.max(0, limit)
1671
+ this.remaining = this.initialLimit
1672
+ }
1673
+
1674
+ public Read(p: $.Bytes): [number, $.GoError] {
1675
+ if (this.err != null) {
1676
+ return [0, this.err]
1677
+ }
1678
+ if ($.len(p) === 0) {
1679
+ return [0, null]
1680
+ }
1681
+ const readLen =
1682
+ $.len(p) - 1 > this.remaining ? this.remaining + 1 : $.len(p)
1683
+ const target = $.goSlice(p, 0, readLen)
1684
+ const [n, err] = this.reader.Read(target)
1685
+ if (n <= this.remaining) {
1686
+ this.remaining -= n
1687
+ this.err = err
1688
+ return [n, err]
1689
+ }
1690
+ const accepted = this.remaining
1691
+ this.remaining = 0
1692
+ this.err = new MaxBytesError({ Limit: this.initialLimit })
1693
+ return [accepted, this.err]
1694
+ }
1695
+
1696
+ public Close(): $.GoError {
1697
+ return this.reader.Close()
1698
+ }
1699
+ }
1700
+
1701
+ export function MaxBytesReader(
1702
+ _w: ResponseWriter | null,
1703
+ r: io.ReadCloser | null,
1704
+ n: number,
1705
+ ): io.ReadCloser {
1706
+ return new maxBytesReader(r ?? NoBody, n)
1707
+ }
1708
+
476
1709
  export class ServeMux implements Handler {
477
1710
  private handlers = new Map<string, Handler>()
478
1711
 
@@ -503,14 +1736,18 @@ export class ServeMux implements Handler {
503
1736
  }
504
1737
  }
505
1738
 
506
- const defaultServeMux = new ServeMux()
1739
+ export const DefaultServeMux = new ServeMux()
507
1740
 
508
1741
  export function NewServeMux(): ServeMux {
509
1742
  return new ServeMux()
510
1743
  }
511
1744
 
1745
+ export function Handle(pattern: string, handler: Handler | null): void {
1746
+ DefaultServeMux.Handle(pattern, handler)
1747
+ }
1748
+
512
1749
  export function HandleFunc(pattern: string, handler: HandlerFunc): void {
513
- defaultServeMux.HandleFunc(pattern, handler)
1750
+ DefaultServeMux.HandleFunc(pattern, handler)
514
1751
  }
515
1752
 
516
1753
  export function StripPrefix(prefix: string, handler: Handler | null): Handler {
@@ -525,6 +1762,52 @@ export function StripPrefix(prefix: string, handler: Handler | null): Handler {
525
1762
  }
526
1763
  }
527
1764
 
1765
+ export function AllowQuerySemicolons(handler: Handler | null): Handler {
1766
+ return handler ?? NotFoundHandler()
1767
+ }
1768
+
1769
+ export function MaxBytesHandler(handler: Handler | null, n: number): Handler {
1770
+ return {
1771
+ ServeHTTP(w, r) {
1772
+ const req = $.pointerValue<Request | null>(r)
1773
+ let wrappedReq = req
1774
+ if (req != null && req.Body != null) {
1775
+ const reqCopy = Object.assign(
1776
+ Object.create(Object.getPrototypeOf(req)),
1777
+ req,
1778
+ ) as Request
1779
+ reqCopy.Body = MaxBytesReader(w, req.Body, n)
1780
+ wrappedReq = reqCopy
1781
+ }
1782
+ return handler?.ServeHTTP(w, wrappedReq)
1783
+ },
1784
+ }
1785
+ }
1786
+
1787
+ export function NotFoundHandler(): Handler {
1788
+ return { ServeHTTP: NotFound }
1789
+ }
1790
+
1791
+ export function RedirectHandler(url: string, code: number): Handler {
1792
+ return {
1793
+ ServeHTTP(w, r) {
1794
+ Redirect(w, r, url, code)
1795
+ },
1796
+ }
1797
+ }
1798
+
1799
+ export function TimeoutHandler(handler: Handler | null, _dt: number, msg: string): Handler {
1800
+ return {
1801
+ ServeHTTP(w, r) {
1802
+ if (handler == null) {
1803
+ Error(w, msg, StatusServiceUnavailable)
1804
+ return
1805
+ }
1806
+ return handler.ServeHTTP(w, r)
1807
+ },
1808
+ }
1809
+ }
1810
+
528
1811
  export function Error(w: ResponseWriter | null, error: string, code: number): void {
529
1812
  w?.WriteHeader(code)
530
1813
  w?.Write($.stringToBytes(error + '\n'))
@@ -555,16 +1838,367 @@ export function ParseTime(text: string): [time.Time, $.GoError] {
555
1838
  return [time.UnixMilli(date.getTime()), null]
556
1839
  }
557
1840
 
1841
+ export function DetectContentType(data: $.Slice<number>): string {
1842
+ const bytes = Uint8Array.from(data ?? []).subarray(0, 512)
1843
+ const firstNonWS = firstNonWhitespace(bytes)
1844
+ const afterWS = bytes.subarray(firstNonWS)
1845
+ if (
1846
+ htmlSig(afterWS, '<!DOCTYPE HTML') ||
1847
+ htmlSig(afterWS, '<HTML') ||
1848
+ htmlSig(afterWS, '<HEAD') ||
1849
+ htmlSig(afterWS, '<SCRIPT') ||
1850
+ htmlSig(afterWS, '<IFRAME') ||
1851
+ htmlSig(afterWS, '<H1') ||
1852
+ htmlSig(afterWS, '<DIV') ||
1853
+ htmlSig(afterWS, '<FONT') ||
1854
+ htmlSig(afterWS, '<TABLE') ||
1855
+ htmlSig(afterWS, '<A') ||
1856
+ htmlSig(afterWS, '<STYLE') ||
1857
+ htmlSig(afterWS, '<TITLE') ||
1858
+ htmlSig(afterWS, '<B') ||
1859
+ htmlSig(afterWS, '<BODY') ||
1860
+ htmlSig(afterWS, '<BR') ||
1861
+ htmlSig(afterWS, '<P') ||
1862
+ startsWithASCII(afterWS, '<!--')
1863
+ ) {
1864
+ return 'text/html; charset=utf-8'
1865
+ }
1866
+ if (startsWithASCII(afterWS, '<?xml')) {
1867
+ return 'text/xml; charset=utf-8'
1868
+ }
1869
+ if (startsWithASCII(bytes, '%PDF-')) {
1870
+ return 'application/pdf'
1871
+ }
1872
+ if (startsWithASCII(bytes, '%!PS-Adobe-')) {
1873
+ return 'application/postscript'
1874
+ }
1875
+ if (startsWithBytes(bytes, new Uint8Array([0xfe, 0xff]))) {
1876
+ return 'text/plain; charset=utf-16be'
1877
+ }
1878
+ if (startsWithBytes(bytes, new Uint8Array([0xff, 0xfe]))) {
1879
+ return 'text/plain; charset=utf-16le'
1880
+ }
1881
+ if (startsWithBytes(bytes, new Uint8Array([0xef, 0xbb, 0xbf]))) {
1882
+ return 'text/plain; charset=utf-8'
1883
+ }
1884
+ if (startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x01, 0x00])) || startsWithBytes(bytes, new Uint8Array([0x00, 0x00, 0x02, 0x00]))) {
1885
+ return 'image/x-icon'
1886
+ }
1887
+ if (startsWithASCII(bytes, 'BM')) {
1888
+ return 'image/bmp'
1889
+ }
1890
+ if (startsWithASCII(bytes, 'GIF87a') || startsWithASCII(bytes, 'GIF89a')) {
1891
+ return 'image/gif'
1892
+ }
1893
+ if (isRIFFSignature(bytes, 'WEBPVP')) {
1894
+ return 'image/webp'
1895
+ }
1896
+ if (startsWithBytes(bytes, new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) {
1897
+ return 'image/png'
1898
+ }
1899
+ if (startsWithBytes(bytes, new Uint8Array([0xff, 0xd8, 0xff]))) {
1900
+ return 'image/jpeg'
1901
+ }
1902
+ if (isFORMSignature(bytes, 'AIFF')) {
1903
+ return 'audio/aiff'
1904
+ }
1905
+ if (startsWithASCII(bytes, 'ID3')) {
1906
+ return 'audio/mpeg'
1907
+ }
1908
+ if (startsWithBytes(bytes, new Uint8Array([0x4f, 0x67, 0x67, 0x53, 0x00]))) {
1909
+ return 'application/ogg'
1910
+ }
1911
+ if (startsWithBytes(bytes, new Uint8Array([0x4d, 0x54, 0x68, 0x64, 0x00, 0x00, 0x00, 0x06]))) {
1912
+ return 'audio/midi'
1913
+ }
1914
+ if (isRIFFSignature(bytes, 'AVI ')) {
1915
+ return 'video/avi'
1916
+ }
1917
+ if (isRIFFSignature(bytes, 'WAVE')) {
1918
+ return 'audio/wave'
1919
+ }
1920
+ if (isMP4Signature(bytes)) {
1921
+ return 'video/mp4'
1922
+ }
1923
+ if (startsWithBytes(bytes, new Uint8Array([0x1a, 0x45, 0xdf, 0xa3]))) {
1924
+ return 'video/webm'
1925
+ }
1926
+ if (isEOTSignature(bytes)) {
1927
+ return 'application/vnd.ms-fontobject'
1928
+ }
1929
+ if (startsWithBytes(bytes, new Uint8Array([0x00, 0x01, 0x00, 0x00]))) {
1930
+ return 'font/ttf'
1931
+ }
1932
+ if (startsWithASCII(bytes, 'OTTO')) {
1933
+ return 'font/otf'
1934
+ }
1935
+ if (startsWithASCII(bytes, 'ttcf')) {
1936
+ return 'font/collection'
1937
+ }
1938
+ if (startsWithASCII(bytes, 'wOFF')) {
1939
+ return 'font/woff'
1940
+ }
1941
+ if (startsWithASCII(bytes, 'wOF2')) {
1942
+ return 'font/woff2'
1943
+ }
1944
+ if (startsWithBytes(bytes, new Uint8Array([0x1f, 0x8b, 0x08]))) {
1945
+ return 'application/x-gzip'
1946
+ }
1947
+ if (startsWithBytes(bytes, new Uint8Array([0x50, 0x4b, 0x03, 0x04]))) {
1948
+ return 'application/zip'
1949
+ }
1950
+ if (startsWithBytes(bytes, new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00]))) {
1951
+ return 'application/x-rar-compressed'
1952
+ }
1953
+ if (startsWithBytes(bytes, new Uint8Array([0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x01, 0x00]))) {
1954
+ return 'application/x-rar-compressed'
1955
+ }
1956
+ if (startsWithBytes(bytes, new Uint8Array([0x00, 0x61, 0x73, 0x6d]))) {
1957
+ return 'application/wasm'
1958
+ }
1959
+ for (const byte of afterWS) {
1960
+ if (byte <= 0x08 || byte === 0x0b || (byte >= 0x0e && byte <= 0x1a) || (byte >= 0x1c && byte <= 0x1f)) {
1961
+ return 'application/octet-stream'
1962
+ }
1963
+ }
1964
+ return 'text/plain; charset=utf-8'
1965
+ }
1966
+
1967
+ function firstNonWhitespace(data: Uint8Array): number {
1968
+ for (let i = 0; i < data.length; i++) {
1969
+ const byte = data[i]
1970
+ if (byte !== 0x09 && byte !== 0x0a && byte !== 0x0c && byte !== 0x0d && byte !== 0x20) {
1971
+ return i
1972
+ }
1973
+ }
1974
+ return data.length
1975
+ }
1976
+
1977
+ function startsWithBytes(data: Uint8Array, prefix: Uint8Array): boolean {
1978
+ if (data.length < prefix.length) {
1979
+ return false
1980
+ }
1981
+ for (let i = 0; i < prefix.length; i++) {
1982
+ if (data[i] !== prefix[i]) {
1983
+ return false
1984
+ }
1985
+ }
1986
+ return true
1987
+ }
1988
+
1989
+ function startsWithASCII(data: Uint8Array, prefix: string): boolean {
1990
+ return asciiMatchesAt(data, 0, prefix)
1991
+ }
1992
+
1993
+ function asciiMatchesAt(data: Uint8Array, offset: number, text: string): boolean {
1994
+ if (data.length < offset + text.length) {
1995
+ return false
1996
+ }
1997
+ for (let i = 0; i < text.length; i++) {
1998
+ if (data[offset + i] !== text.charCodeAt(i)) {
1999
+ return false
2000
+ }
2001
+ }
2002
+ return true
2003
+ }
2004
+
2005
+ function isRIFFSignature(data: Uint8Array, form: string): boolean {
2006
+ return asciiMatchesAt(data, 0, 'RIFF') && asciiMatchesAt(data, 8, form)
2007
+ }
2008
+
2009
+ function isFORMSignature(data: Uint8Array, form: string): boolean {
2010
+ return asciiMatchesAt(data, 0, 'FORM') && asciiMatchesAt(data, 8, form)
2011
+ }
2012
+
2013
+ function isMP4Signature(data: Uint8Array): boolean {
2014
+ if (data.length < 12 || !asciiMatchesAt(data, 4, 'ftyp')) {
2015
+ return false
2016
+ }
2017
+ const boxSize =
2018
+ data[0] * 0x1000000 + data[1] * 0x10000 + data[2] * 0x100 + data[3]
2019
+ if (boxSize < 12 || data.length < boxSize || boxSize % 4 !== 0) {
2020
+ return false
2021
+ }
2022
+ for (let offset = 8; offset + 3 <= boxSize; offset += 4) {
2023
+ if (offset === 12) {
2024
+ continue
2025
+ }
2026
+ if (asciiMatchesAt(data, offset, 'mp4')) {
2027
+ return true
2028
+ }
2029
+ }
2030
+ return false
2031
+ }
2032
+
2033
+ function isEOTSignature(data: Uint8Array): boolean {
2034
+ return data.length >= 36 && data[34] === 0x4c && data[35] === 0x50
2035
+ }
2036
+
2037
+ function htmlSig(data: Uint8Array, sig: string): boolean {
2038
+ if (data.length < sig.length + 1) {
2039
+ return false
2040
+ }
2041
+ for (let i = 0; i < sig.length; i++) {
2042
+ const got = data[i] >= 0x61 && data[i] <= 0x7a ? data[i] & 0xdf : data[i]
2043
+ if (got !== sig.charCodeAt(i)) {
2044
+ return false
2045
+ }
2046
+ }
2047
+ const term = data[sig.length]
2048
+ return term === 0x20 || term === 0x3e
2049
+ }
2050
+
2051
+ export function ParseHTTPVersion(vers: string): [number, number, boolean] {
2052
+ const match = /^HTTP\/(\d+)\.(\d+)$/.exec(vers)
2053
+ if (match == null) {
2054
+ return [0, 0, false]
2055
+ }
2056
+ return [Number(match[1]), Number(match[2]), true]
2057
+ }
2058
+
2059
+ export function ParseCookie(line: string): [$.Slice<Cookie | null>, $.GoError] {
2060
+ const parts = line.trim().split(';')
2061
+ if (parts.length > 3000) {
2062
+ return [null, errCookieNumLimitExceeded]
2063
+ }
2064
+ if (parts.length === 1 && parts[0] === '') {
2065
+ return [null, errBlankCookie]
2066
+ }
2067
+ const cookies: Array<Cookie | null> = []
2068
+ for (const raw of parts) {
2069
+ const part = raw.trim()
2070
+ const eq = part.indexOf('=')
2071
+ if (eq < 0) {
2072
+ return [null, errEqualNotFoundInCookie]
2073
+ }
2074
+ const name = part.slice(0, eq)
2075
+ if (!isToken(name)) {
2076
+ return [null, errInvalidCookieName]
2077
+ }
2078
+ const [value, quoted, ok] = parseCookieValue(part.slice(eq + 1), true)
2079
+ if (!ok) {
2080
+ return [null, errInvalidCookieValue]
2081
+ }
2082
+ cookies.push(new Cookie({ Name: name, Value: value, Quoted: quoted }))
2083
+ }
2084
+ return [$.arrayToSlice(cookies), null]
2085
+ }
2086
+
2087
+ export function ParseSetCookie(line: string): [Cookie | null, $.GoError] {
2088
+ const parts = line.trim().split(';')
2089
+ if (parts.length === 1 && parts[0] === '') {
2090
+ return [null, errBlankCookie]
2091
+ }
2092
+ const first = parts[0].trim()
2093
+ const eq = first.indexOf('=')
2094
+ if (eq < 0) {
2095
+ return [null, errEqualNotFoundInCookie]
2096
+ }
2097
+ const name = first.slice(0, eq).trim()
2098
+ if (!isToken(name)) {
2099
+ return [null, errInvalidCookieName]
2100
+ }
2101
+ const [value, quoted, ok] = parseCookieValue(first.slice(eq + 1), true)
2102
+ if (!ok) {
2103
+ return [null, errInvalidCookieValue]
2104
+ }
2105
+ const cookie = new Cookie({ Name: name, Value: value, Quoted: quoted, Raw: line })
2106
+ const unparsed: string[] = []
2107
+ for (const raw of parts.slice(1)) {
2108
+ const part = raw.trim()
2109
+ if (part === '') {
2110
+ continue
2111
+ }
2112
+ const attrEq = part.indexOf('=')
2113
+ const attr = attrEq < 0 ? part : part.slice(0, attrEq)
2114
+ const rawValue = attrEq < 0 ? '' : part.slice(attrEq + 1)
2115
+ const [lowerAttr, ascii] = asciiLower(attr)
2116
+ if (!ascii) {
2117
+ continue
2118
+ }
2119
+ const [attrValue, , attrOK] = parseCookieValue(rawValue, false)
2120
+ if (!attrOK) {
2121
+ unparsed.push(part)
2122
+ continue
2123
+ }
2124
+ switch (lowerAttr) {
2125
+ case 'samesite': {
2126
+ const [lowerValue, valueASCII] = asciiLower(attrValue)
2127
+ if (!valueASCII) {
2128
+ cookie.SameSite = SameSiteDefaultMode
2129
+ continue
2130
+ }
2131
+ switch (lowerValue) {
2132
+ case 'lax':
2133
+ cookie.SameSite = SameSiteLaxMode
2134
+ break
2135
+ case 'strict':
2136
+ cookie.SameSite = SameSiteStrictMode
2137
+ break
2138
+ case 'none':
2139
+ cookie.SameSite = SameSiteNoneMode
2140
+ break
2141
+ default:
2142
+ cookie.SameSite = SameSiteDefaultMode
2143
+ break
2144
+ }
2145
+ continue
2146
+ }
2147
+ case 'secure':
2148
+ cookie.Secure = true
2149
+ continue
2150
+ case 'httponly':
2151
+ cookie.HttpOnly = true
2152
+ continue
2153
+ case 'domain':
2154
+ cookie.Domain = attrValue
2155
+ continue
2156
+ case 'max-age': {
2157
+ if (!/^[+-]?\d+$/.test(attrValue)) {
2158
+ break
2159
+ }
2160
+ let secs = Number.parseInt(attrValue, 10)
2161
+ if ((secs !== 0 && attrValue[0] === '0') || !Number.isSafeInteger(secs)) {
2162
+ break
2163
+ }
2164
+ if (secs <= 0) {
2165
+ secs = -1
2166
+ }
2167
+ cookie.MaxAge = secs
2168
+ continue
2169
+ }
2170
+ case 'expires': {
2171
+ cookie.RawExpires = attrValue
2172
+ const parsed = new globalThis.Date(attrValue)
2173
+ if (Number.isNaN(parsed.getTime())) {
2174
+ break
2175
+ }
2176
+ cookie.Expires = time.UnixMilli(parsed.getTime())
2177
+ continue
2178
+ }
2179
+ case 'path':
2180
+ cookie.Path = attrValue
2181
+ continue
2182
+ case 'partitioned':
2183
+ cookie.Partitioned = true
2184
+ continue
2185
+ }
2186
+ unparsed.push(part)
2187
+ }
2188
+ cookie.Unparsed = unparsed.length === 0 ? null : $.arrayToSlice(unparsed)
2189
+ return [cookie, null]
2190
+ }
2191
+
558
2192
  export function NewRequest(
559
2193
  method: string,
560
2194
  url: string,
561
2195
  body: io.Reader | null,
562
2196
  ): [Request | null, $.GoError] {
563
- return NewRequestWithContext(null, method, url, body)
2197
+ return NewRequestWithContext(context.Background(), method, url, body)
564
2198
  }
565
2199
 
566
2200
  export function NewRequestWithContext(
567
- _ctx: context.Context,
2201
+ ctx: context.Context,
568
2202
  method: string,
569
2203
  url: string,
570
2204
  body: io.Reader | null,
@@ -572,10 +2206,114 @@ export function NewRequestWithContext(
572
2206
  if (method === '') {
573
2207
  method = MethodGet
574
2208
  }
575
- const parsedURL = parseRequestURL(url)
576
- return [new Request({ Method: method, URL: parsedURL, Body: body, RequestURI: parsedURL.Path, ctx: _ctx }), null]
2209
+ if (!isToken(method)) {
2210
+ return [null, errors.New(`net/http: invalid method ${JSON.stringify(method)}`)]
2211
+ }
2212
+ if (ctx == null) {
2213
+ return [null, errors.New('net/http: nil Context')]
2214
+ }
2215
+ const [parsedURL, err] = parseRequestURL(url)
2216
+ if (err != null || parsedURL == null) {
2217
+ return [null, err]
2218
+ }
2219
+ const bodyInfo = requestBodyInfo(body, 0)
2220
+ return [new Request({ Method: method, URL: parsedURL, Body: bodyInfo.Body, ContentLength: bodyInfo.ContentLength, Host: parsedURL.Host, ctx }), null]
2221
+ }
2222
+
2223
+ export async function Get(_url: string): Promise<[Response | null, $.GoError]> {
2224
+ const [req, err] = NewRequest(MethodGet, _url, null)
2225
+ if (err != null) {
2226
+ return [null, err]
2227
+ }
2228
+ return await DefaultClient.Do(req)
2229
+ }
2230
+
2231
+ export async function Head(_url: string): Promise<[Response | null, $.GoError]> {
2232
+ const [req, err] = NewRequest(MethodHead, _url, null)
2233
+ if (err != null) {
2234
+ return [null, err]
2235
+ }
2236
+ return await DefaultClient.Do(req)
2237
+ }
2238
+
2239
+ export async function Post(
2240
+ _url: string,
2241
+ contentType: string,
2242
+ body: io.Reader | null,
2243
+ ): Promise<[Response | null, $.GoError]> {
2244
+ const [req, err] = NewRequest(MethodPost, _url, body)
2245
+ if (err != null || req == null) {
2246
+ return [null, err]
2247
+ }
2248
+ Header_Set(req.Header, 'Content-Type', contentType)
2249
+ return await DefaultClient.Do(req)
2250
+ }
2251
+
2252
+ export async function PostForm(_url: string, data: any): Promise<[Response | null, $.GoError]> {
2253
+ return await DefaultClient.PostForm(_url, data)
2254
+ }
2255
+
2256
+ export function ProxyFromEnvironment(_req: Request | $.VarRef<Request> | null): [any, $.GoError] {
2257
+ return [null, null]
2258
+ }
2259
+
2260
+ export function ProxyURL(fixedURL: any): (req: Request | $.VarRef<Request> | null) => [any, $.GoError] {
2261
+ return () => [fixedURL, null]
2262
+ }
2263
+
2264
+ export function ReadRequest(_reader: any): [Request | null, $.GoError] {
2265
+ return [null, ErrNotSupported]
2266
+ }
2267
+
2268
+ export function ReadResponse(_reader: any, _req: Request | $.VarRef<Request> | null): [Response | null, $.GoError] {
2269
+ return [null, ErrNotSupported]
2270
+ }
2271
+
2272
+ export async function ServeContent(
2273
+ w: ResponseWriter | null,
2274
+ req: Request | $.VarRef<Request> | null,
2275
+ _name: string,
2276
+ _modtime: time.Time,
2277
+ content: io.Reader | null,
2278
+ ): Promise<void> {
2279
+ if (content == null) {
2280
+ NotFound(w, req)
2281
+ return
2282
+ }
2283
+ const [data, err] = await io.ReadAll(content)
2284
+ if (err != null) {
2285
+ Error(w, err.Error(), StatusInternalServerError)
2286
+ return
2287
+ }
2288
+ w?.WriteHeader(StatusOK)
2289
+ const request = $.pointerValueOrNil(req)
2290
+ if (request?.Method !== MethodHead) {
2291
+ w?.Write(data)
2292
+ }
577
2293
  }
578
2294
 
579
- export function Get(_url: string): [Response | null, $.GoError] {
580
- return [null, errors.New('net/http: Get is not implemented in GoScript')]
2295
+ function readCloserForBody(body: io.Reader | null): io.ReadCloser | null {
2296
+ if (body == null) {
2297
+ return null
2298
+ }
2299
+ const closer = body as io.Reader & Partial<io.Closer>
2300
+ if (typeof closer.Close === 'function') {
2301
+ return closer as io.ReadCloser
2302
+ }
2303
+ return io.NopCloser(body)
2304
+ }
2305
+
2306
+ function requestBodyInfo(body: io.Reader | null, unknownLength: number): { Body: io.ReadCloser | null; ContentLength: number } {
2307
+ if (body == null) {
2308
+ return { Body: null, ContentLength: 0 }
2309
+ }
2310
+ const value = $.pointerValueOrNil<any>(body as any)
2311
+ if (value === NoBody) {
2312
+ return { Body: NoBody, ContentLength: 0 }
2313
+ }
2314
+ if (value instanceof bytes.Buffer || value instanceof bytes.Reader || value instanceof strings.Reader) {
2315
+ const length = value.Len()
2316
+ return { Body: length === 0 ? NoBody : readCloserForBody(body), ContentLength: length }
2317
+ }
2318
+ return { Body: readCloserForBody(body), ContentLength: unknownLength }
581
2319
  }