mt-lang 0.2.0 → 0.2.1

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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/docs/index.html +4 -3
  3. data/lib/milk_tea/base.rb +1 -1
  4. data/std/asset_pack.mt +267 -0
  5. data/std/async/libuv_runtime.mt +539 -0
  6. data/std/async/mailbox.mt +191 -0
  7. data/std/async/runtime.mt +87 -0
  8. data/std/async.mt +87 -0
  9. data/std/base64.mt +185 -0
  10. data/std/behavior_tree.mt +396 -0
  11. data/std/binary.mt +314 -0
  12. data/std/binary_heap.mt +157 -0
  13. data/std/bitset.mt +239 -0
  14. data/std/box2d.mt +535 -0
  15. data/std/bytes.mt +46 -0
  16. data/std/c/box2d.mt +1254 -0
  17. data/std/c/cgltf.mt +647 -0
  18. data/std/c/cjson.mt +106 -0
  19. data/std/c/crypto.mt +11 -0
  20. data/std/c/crypto_support.h +51 -0
  21. data/std/c/ctype.mt +19 -0
  22. data/std/c/ctype_bindgen.h +29 -0
  23. data/std/c/curl.mt +1223 -0
  24. data/std/c/enet.mt +473 -0
  25. data/std/c/errno.mt +41 -0
  26. data/std/c/errno_bindgen.h +53 -0
  27. data/std/c/flecs.mt +2945 -0
  28. data/std/c/fs.linux.mt +43 -0
  29. data/std/c/fs.windows.mt +43 -0
  30. data/std/c/fs_support.h +954 -0
  31. data/std/c/gl.mt +2065 -0
  32. data/std/c/gl_registry_helpers.h +10748 -0
  33. data/std/c/glfw.mt +499 -0
  34. data/std/c/libc.mt +167 -0
  35. data/std/c/libuv.mt +1491 -0
  36. data/std/c/math.mt +22 -0
  37. data/std/c/math_bindgen.h +31 -0
  38. data/std/c/miniaudio.mt +3420 -0
  39. data/std/c/pcre2.mt +735 -0
  40. data/std/c/process.mt +58 -0
  41. data/std/c/process_support.h +1147 -0
  42. data/std/c/raygui.mt +1602 -0
  43. data/std/c/raylib.mt +1245 -0
  44. data/std/c/raymath.mt +163 -0
  45. data/std/c/rlgl.mt +434 -0
  46. data/std/c/rpng.mt +60 -0
  47. data/std/c/rres.mt +187 -0
  48. data/std/c/sdl3.mt +4132 -0
  49. data/std/c/sqlite3.mt +951 -0
  50. data/std/c/stb_image.mt +52 -0
  51. data/std/c/stb_image_resize2.mt +128 -0
  52. data/std/c/stb_image_write.mt +19 -0
  53. data/std/c/stb_rect_pack.mt +38 -0
  54. data/std/c/stb_truetype.mt +175 -0
  55. data/std/c/stb_vorbis.mt +31 -0
  56. data/std/c/stdio.mt +90 -0
  57. data/std/c/steamworks.h +5769 -0
  58. data/std/c/steamworks.mt +4771 -0
  59. data/std/c/string.mt +16 -0
  60. data/std/c/string_bindgen.h +36 -0
  61. data/std/c/sync.mt +33 -0
  62. data/std/c/sync_support.h +219 -0
  63. data/std/c/terminal.mt +24 -0
  64. data/std/c/terminal_support.h +408 -0
  65. data/std/c/time.mt +26 -0
  66. data/std/c/tls.mt +24 -0
  67. data/std/c/tls_support.h +626 -0
  68. data/std/c/tracy.mt +53 -0
  69. data/std/c/zlib.mt +20 -0
  70. data/std/c/zlib_support.h +239 -0
  71. data/std/c/zstd.mt +231 -0
  72. data/std/cell.mt +60 -0
  73. data/std/cgltf.mt +115 -0
  74. data/std/cjson.mt +90 -0
  75. data/std/cli.mt +768 -0
  76. data/std/cookie.mt +198 -0
  77. data/std/counter.mt +160 -0
  78. data/std/crypto.mt +74 -0
  79. data/std/cstring.mt +13 -0
  80. data/std/ctype.mt +52 -0
  81. data/std/curl/runtime.mt +261 -0
  82. data/std/curl.mt +38 -0
  83. data/std/deque.mt +348 -0
  84. data/std/encoding.mt +93 -0
  85. data/std/enet.mt +133 -0
  86. data/std/env.mt +41 -0
  87. data/std/errno.mt +47 -0
  88. data/std/flecs.mt +978 -0
  89. data/std/fmt.mt +271 -0
  90. data/std/fs.linux.mt +696 -0
  91. data/std/fs.windows.mt +711 -0
  92. data/std/fsm.mt +267 -0
  93. data/std/gl.mt +2062 -0
  94. data/std/glfw.mt +471 -0
  95. data/std/goap.mt +342 -0
  96. data/std/graph.mt +521 -0
  97. data/std/gzip.mt +77 -0
  98. data/std/hash.mt +429 -0
  99. data/std/http/server.mt +810 -0
  100. data/std/http.mt +1227 -0
  101. data/std/intern.mt +53 -0
  102. data/std/jobs.mt +288 -0
  103. data/std/json.mt +627 -0
  104. data/std/libc.mt +34 -0
  105. data/std/libuv.mt +554 -0
  106. data/std/linear_algebra.mt +243 -0
  107. data/std/linked_map.mt +372 -0
  108. data/std/linked_map_view.mt +49 -0
  109. data/std/linked_set.mt +117 -0
  110. data/std/log.mt +69 -0
  111. data/std/map.mt +418 -0
  112. data/std/math.mt +24 -0
  113. data/std/mem/arena.mt +137 -0
  114. data/std/mem/endian.mt +51 -0
  115. data/std/mem/heap.mt +298 -0
  116. data/std/mem/pool.mt +164 -0
  117. data/std/mem/stack.mt +47 -0
  118. data/std/mem/tracking.mt +119 -0
  119. data/std/miniaudio.mt +1299 -0
  120. data/std/multiset.mt +191 -0
  121. data/std/net/channel.mt +727 -0
  122. data/std/net/clock.mt +237 -0
  123. data/std/net/discovery.mt +277 -0
  124. data/std/net/lobby.mt +813 -0
  125. data/std/net/manager.mt +539 -0
  126. data/std/net/mux.mt +839 -0
  127. data/std/net/nat.mt +122 -0
  128. data/std/net/packet.mt +219 -0
  129. data/std/net/punch.mt +162 -0
  130. data/std/net/rpc.mt +119 -0
  131. data/std/net/session.mt +1157 -0
  132. data/std/net/stun.mt +252 -0
  133. data/std/net/sync.mt +183 -0
  134. data/std/net/turn.mt +459 -0
  135. data/std/net.mt +2944 -0
  136. data/std/oauth2.mt +408 -0
  137. data/std/option.mt +51 -0
  138. data/std/ordered_map.mt +497 -0
  139. data/std/ordered_set.mt +348 -0
  140. data/std/path.mt +368 -0
  141. data/std/pcre2/runtime.mt +35 -0
  142. data/std/pcre2.mt +78 -0
  143. data/std/priority_queue.mt +55 -0
  144. data/std/process.mt +837 -0
  145. data/std/queue.mt +55 -0
  146. data/std/random.mt +146 -0
  147. data/std/raygui.mt +99 -0
  148. data/std/raylib/debug_console.mt +71 -0
  149. data/std/raylib/easing.mt +388 -0
  150. data/std/raylib/packed_assets.mt +255 -0
  151. data/std/raylib/runtime.mt +30 -0
  152. data/std/raylib/tracy_gpu.mt +37 -0
  153. data/std/raylib.mt +1510 -0
  154. data/std/raymath.mt +153 -0
  155. data/std/result.mt +89 -0
  156. data/std/rlgl.mt +268 -0
  157. data/std/rpng.mt +46 -0
  158. data/std/rres.mt +36 -0
  159. data/std/sdl3/runtime.mt +54 -0
  160. data/std/sdl3.mt +1731 -0
  161. data/std/serialize.mt +68 -0
  162. data/std/set.mt +124 -0
  163. data/std/spatial.mt +176 -0
  164. data/std/sqlite3.mt +151 -0
  165. data/std/stack.mt +55 -0
  166. data/std/stb_image.mt +47 -0
  167. data/std/stb_image_resize2.mt +41 -0
  168. data/std/stb_image_write.mt +17 -0
  169. data/std/stb_rect_pack.mt +15 -0
  170. data/std/stb_truetype.mt +77 -0
  171. data/std/stb_vorbis.mt +16 -0
  172. data/std/stdio.mt +88 -0
  173. data/std/steamworks.mt +1542 -0
  174. data/std/str.mt +293 -0
  175. data/std/string.mt +234 -0
  176. data/std/sync.mt +194 -0
  177. data/std/tar.mt +704 -0
  178. data/std/terminal.mt +1002 -0
  179. data/std/testing.mt +266 -0
  180. data/std/thread.mt +120 -0
  181. data/std/time.mt +105 -0
  182. data/std/tls.mt +616 -0
  183. data/std/toml.mt +1310 -0
  184. data/std/tracy.mt +42 -0
  185. data/std/uri.mt +118 -0
  186. data/std/url.mt +372 -0
  187. data/std/vec.mt +433 -0
  188. data/std/zstd.mt +94 -0
  189. metadata +187 -2
data/std/http.mt ADDED
@@ -0,0 +1,1227 @@
1
+ import std.bytes as bytes
2
+ import std.fmt as fmt
3
+ import std.mem.heap as heap
4
+ import std.net as net
5
+ import std.str as text
6
+ import std.string as string
7
+ import std.tls as tls
8
+ import std.vec as vec
9
+
10
+ public struct Error:
11
+ message: string.String
12
+
13
+ public struct Header:
14
+ name: string.String
15
+ value: string.String
16
+
17
+ public struct RequestHeader:
18
+ name: str
19
+ value: str
20
+
21
+ public struct Response:
22
+ status_code: int
23
+ reason: string.String
24
+ headers: vec.Vec[Header]
25
+ body: bytes.Bytes
26
+
27
+ enum UrlScheme: int
28
+ http = 1
29
+ https = 2
30
+
31
+ struct ParsedUrl:
32
+ scheme: UrlScheme
33
+ host: string.String
34
+ authority: string.String
35
+ target: string.String
36
+ port: int
37
+
38
+ struct ResponseHead:
39
+ status_code: int
40
+ reason: string.String
41
+ headers: vec.Vec[Header]
42
+ content_length: Option[ptr_uint]
43
+ chunked: bool
44
+
45
+
46
+ function status_error(message: str) -> Error:
47
+ return Error(message = string.String.from_str(message))
48
+
49
+
50
+ function status_net_error(net_error: net.Error) -> Error:
51
+ var owned_error = net_error
52
+ let message = string.String.from_str(owned_error.message.as_str())
53
+ owned_error.release()
54
+ return Error(message = message)
55
+
56
+
57
+ function status_tls_error(tls_error: tls.Error) -> Error:
58
+ var owned_error = tls_error
59
+ let message = string.String.from_str(owned_error.message.as_str())
60
+ owned_error.release()
61
+ return Error(message = message)
62
+
63
+
64
+ function url_error(detail: str) -> Error:
65
+ return status_error(f"invalid http url: #{detail}")
66
+
67
+
68
+ function response_error(detail: str) -> Error:
69
+ return status_error(f"invalid http response: #{detail}")
70
+
71
+
72
+ function transport_error(detail: str) -> Error:
73
+ return status_error(detail)
74
+
75
+
76
+ function ascii_fold(value: ubyte) -> ubyte:
77
+ if value >= 65 and value <= 90:
78
+ return value + 32
79
+
80
+ return value
81
+
82
+
83
+ function ascii_case_equal(left: str, right: str) -> bool:
84
+ if left.len != right.len:
85
+ return false
86
+
87
+ var index: ptr_uint = 0
88
+ while index < left.len:
89
+ if ascii_fold(left.byte_at(index)) != ascii_fold(right.byte_at(index)):
90
+ return false
91
+ index += 1
92
+
93
+ return true
94
+
95
+
96
+ function find_byte_from(text_value: str, value: ubyte, start: ptr_uint) -> Option[ptr_uint]:
97
+ var index = start
98
+ while index < text_value.len:
99
+ if text_value.byte_at(index) == value:
100
+ return Option[ptr_uint].some(value = index)
101
+ index += 1
102
+
103
+ return Option[ptr_uint].none
104
+
105
+
106
+ function count_byte(text_value: str, value: ubyte) -> ptr_uint:
107
+ var count: ptr_uint = 0
108
+ var index: ptr_uint = 0
109
+ while index < text_value.len:
110
+ if text_value.byte_at(index) == value:
111
+ count += 1
112
+ index += 1
113
+
114
+ return count
115
+
116
+
117
+ function parse_decimal(text_value: str) -> Option[ptr_uint]:
118
+ if text_value.len == 0:
119
+ return Option[ptr_uint].none
120
+
121
+ var value: ptr_uint = 0
122
+ var index: ptr_uint = 0
123
+ while index < text_value.len:
124
+ let current = text_value.byte_at(index)
125
+ if current < 48 or current > 57:
126
+ return Option[ptr_uint].none
127
+
128
+ let digit = ptr_uint<-(current - 48ub)
129
+ if value > (heap.ptr_uint_max - digit) / 10:
130
+ return Option[ptr_uint].none
131
+
132
+ value = value * 10 + digit
133
+ index += 1
134
+
135
+ return Option[ptr_uint].some(value = value)
136
+
137
+
138
+ function parse_port(text_value: str) -> Option[int]:
139
+ let parsed = parse_decimal(text_value) else:
140
+ return Option[int].none
141
+
142
+ if parsed > 65535:
143
+ return Option[int].none
144
+
145
+ return Option[int].some(value = int<-parsed)
146
+
147
+
148
+ function parse_target(rest: str) -> Result[string.String, Error]:
149
+ if rest.len == 0:
150
+ return Result[string.String, Error].success(value = string.String.from_str("/"))
151
+
152
+ let fragment_index = find_byte_from(rest, 35, 0)
153
+ var target_text = rest
154
+ match fragment_index:
155
+ Option.none:
156
+ pass
157
+ Option.some as payload:
158
+ target_text = rest.slice(0, payload.value)
159
+
160
+ if target_text.len == 0:
161
+ return Result[string.String, Error].success(value = string.String.from_str("/"))
162
+
163
+ let first = target_text.byte_at(0)
164
+ if first == 47:
165
+ return Result[string.String, Error].success(value = string.String.from_str(target_text))
166
+
167
+ if first == 63:
168
+ var target = string.String.from_str("/")
169
+ target.append(target_text)
170
+ return Result[string.String, Error].success(value = target)
171
+
172
+ return Result[string.String, Error].failure(error = url_error("path must start with '/' or '?'") )
173
+
174
+
175
+ function default_port_for_scheme(scheme: UrlScheme) -> int:
176
+ match scheme:
177
+ UrlScheme.http:
178
+ return 80
179
+ UrlScheme.https:
180
+ return 443
181
+
182
+
183
+ function parse_url(url: str) -> Result[ParsedUrl, Error]:
184
+ var scheme = UrlScheme.http
185
+ var remainder = url
186
+ if url.starts_with("http://"):
187
+ remainder = url.slice(7, url.len - 7)
188
+ else if url.starts_with("https://"):
189
+ scheme = UrlScheme.https
190
+ remainder = url.slice(8, url.len - 8)
191
+ else:
192
+ return Result[ParsedUrl, Error].failure(error = url_error("URL must start with http:// or https://"))
193
+
194
+ if remainder.len == 0:
195
+ return Result[ParsedUrl, Error].failure(error = url_error("missing authority"))
196
+
197
+ var authority_end = remainder.len
198
+ var index: ptr_uint = 0
199
+ while index < remainder.len:
200
+ let current = remainder.byte_at(index)
201
+ if current == 47 or current == 63 or current == 35:
202
+ authority_end = index
203
+ break
204
+ index += 1
205
+
206
+ let authority_text = remainder.slice(0, authority_end)
207
+ if authority_text.len == 0:
208
+ return Result[ParsedUrl, Error].failure(error = url_error("missing authority"))
209
+
210
+ if count_byte(authority_text, 64) != 0:
211
+ return Result[ParsedUrl, Error].failure(error = url_error("userinfo is not supported"))
212
+
213
+ var host_text = authority_text
214
+ var port = default_port_for_scheme(scheme)
215
+ if authority_text.byte_at(0) == 91:
216
+ var closing_bracket: ptr_uint = 0
217
+ match find_byte_from(authority_text, 93, 1):
218
+ Option.none:
219
+ return Result[ParsedUrl, Error].failure(error = url_error("missing closing ']' for IPv6 host"))
220
+ Option.some as payload:
221
+ closing_bracket = payload.value
222
+
223
+ if closing_bracket == 1:
224
+ return Result[ParsedUrl, Error].failure(error = url_error("host is empty"))
225
+
226
+ host_text = authority_text.slice(1, closing_bracket - 1)
227
+ if closing_bracket + 1 < authority_text.len:
228
+ if authority_text.byte_at(closing_bracket + 1) != 58:
229
+ return Result[ParsedUrl, Error].failure(error = url_error("unexpected text after IPv6 host"))
230
+
231
+ let port_start = closing_bracket + 2
232
+ let port_text = authority_text.slice(port_start, authority_text.len - port_start)
233
+ match parse_port(port_text):
234
+ Option.none:
235
+ return Result[ParsedUrl, Error].failure(error = url_error("invalid port"))
236
+ Option.some as payload:
237
+ port = payload.value
238
+ else:
239
+ let colon_count = count_byte(authority_text, 58)
240
+ if colon_count > 1:
241
+ return Result[ParsedUrl, Error].failure(error = url_error("IPv6 hosts must be wrapped in []"))
242
+
243
+ if colon_count == 1:
244
+ var colon_index: ptr_uint = 0
245
+ match authority_text.find_byte(58):
246
+ Option.none:
247
+ fatal(c"http.parse_url missing authority port separator")
248
+ Option.some as payload:
249
+ colon_index = payload.value
250
+
251
+ if colon_index == 0:
252
+ return Result[ParsedUrl, Error].failure(error = url_error("host is empty"))
253
+
254
+ host_text = authority_text.slice(0, colon_index)
255
+ let port_start = colon_index + 1
256
+ let port_text = authority_text.slice(port_start, authority_text.len - port_start)
257
+ match parse_port(port_text):
258
+ Option.none:
259
+ return Result[ParsedUrl, Error].failure(error = url_error("invalid port"))
260
+ Option.some as payload:
261
+ port = payload.value
262
+
263
+ if host_text.len == 0:
264
+ return Result[ParsedUrl, Error].failure(error = url_error("host is empty"))
265
+
266
+ let rest = remainder.slice(authority_end, remainder.len - authority_end)
267
+ let target_result = parse_target(rest)
268
+ match target_result:
269
+ Result.failure as payload:
270
+ return Result[ParsedUrl, Error].failure(error = payload.error)
271
+ Result.success as payload:
272
+ let target = payload.value
273
+ return Result[ParsedUrl, Error].success(
274
+ value = ParsedUrl(
275
+ scheme = scheme,
276
+ host = string.String.from_str(host_text),
277
+ authority = string.String.from_str(authority_text),
278
+ target = target,
279
+ port = port
280
+ )
281
+ )
282
+
283
+
284
+ function append_request_text(output: ref[vec.Vec[ubyte]], value: str) -> void:
285
+ output.append_span(text.as_byte_span(value))
286
+
287
+
288
+ function append_request_header_line(output: ref[vec.Vec[ubyte]], name: str, value: str) -> void:
289
+ append_request_text(output, name)
290
+ append_request_text(output, ": ")
291
+ append_request_text(output, value)
292
+ append_request_text(output, "\r\n")
293
+
294
+
295
+ function valid_http_token(value: str) -> bool:
296
+ if value.len == 0:
297
+ return false
298
+
299
+ var index: ptr_uint = 0
300
+ while index < value.len:
301
+ let current = value.byte_at(index)
302
+ if current <= 32 or current >= 127 or current == 58:
303
+ return false
304
+ index += 1
305
+
306
+ return true
307
+
308
+
309
+ function valid_http_header_value(value: str) -> bool:
310
+ var index: ptr_uint = 0
311
+ while index < value.len:
312
+ let current = value.byte_at(index)
313
+ if current == 13 or current == 10:
314
+ return false
315
+ index += 1
316
+
317
+ return true
318
+
319
+
320
+ function build_request(
321
+ url: ParsedUrl,
322
+ method: str,
323
+ headers: span[RequestHeader],
324
+ body: Option[span[ubyte]]
325
+ ) -> Result[vec.Vec[ubyte], Error]:
326
+ if not valid_http_token(method):
327
+ return Result[vec.Vec[ubyte], Error].failure(error = status_error("invalid http method"))
328
+
329
+ var body_length: ptr_uint = 0
330
+ match body:
331
+ Option.none:
332
+ pass
333
+ Option.some as payload:
334
+ body_length = payload.value.len
335
+
336
+ var request = vec.Vec[ubyte].with_capacity(url.target.len + url.authority.len + method.len + body_length + 128)
337
+
338
+ var has_host = false
339
+ var has_connection = false
340
+ var has_user_agent = false
341
+ var content_length_seen = false
342
+
343
+ var index: ptr_uint = 0
344
+ while index < headers.len:
345
+ let header = unsafe: read(headers.data + index)
346
+ if not valid_http_token(header.name):
347
+ request.release()
348
+ return Result[vec.Vec[ubyte], Error].failure(error = status_error("request header name is invalid"))
349
+
350
+ if not valid_http_header_value(header.value):
351
+ request.release()
352
+ return Result[vec.Vec[ubyte], Error].failure(error = status_error("request header value is invalid"))
353
+
354
+ if ascii_case_equal(header.name, "Host"):
355
+ has_host = true
356
+ else if ascii_case_equal(header.name, "Connection"):
357
+ has_connection = true
358
+ else if ascii_case_equal(header.name, "User-Agent"):
359
+ has_user_agent = true
360
+ else if ascii_case_equal(header.name, "Content-Length"):
361
+ if content_length_seen:
362
+ request.release()
363
+ return Result[
364
+ vec.Vec[ubyte],
365
+ Error
366
+ ].failure(error = status_error("request Content-Length must not be repeated"))
367
+
368
+ let content_length = parse_decimal(header.value.trim_ascii_whitespace()) else:
369
+ request.release()
370
+ return Result[
371
+ vec.Vec[ubyte],
372
+ Error
373
+ ].failure(error = status_error("request Content-Length must be a decimal integer"))
374
+
375
+ if content_length != body_length:
376
+ request.release()
377
+ return Result[
378
+ vec.Vec[ubyte],
379
+ Error
380
+ ].failure(error = status_error("request Content-Length does not match body length"))
381
+
382
+ content_length_seen = true
383
+
384
+ index += 1
385
+
386
+ append_request_text(ref_of(request), method)
387
+ append_request_text(ref_of(request), " ")
388
+ append_request_text(ref_of(request), url.target.as_str())
389
+ append_request_text(ref_of(request), " HTTP/1.1\r\n")
390
+
391
+ if not has_host:
392
+ append_request_header_line(ref_of(request), "Host", url.authority.as_str())
393
+
394
+ if not has_connection:
395
+ append_request_header_line(ref_of(request), "Connection", "close")
396
+
397
+ if not has_user_agent:
398
+ append_request_header_line(ref_of(request), "User-Agent", "milk-tea/std.http")
399
+
400
+ if not content_length_seen:
401
+ var content_length = fmt.to_string_ptr_uint(body_length)
402
+ defer content_length.release()
403
+ append_request_header_line(ref_of(request), "Content-Length", content_length.as_str())
404
+
405
+ index = 0
406
+ while index < headers.len:
407
+ let header = unsafe: read(headers.data + index)
408
+ append_request_header_line(ref_of(request), header.name, header.value)
409
+ index += 1
410
+
411
+ append_request_text(ref_of(request), "\r\n")
412
+
413
+ match body:
414
+ Option.none:
415
+ pass
416
+ Option.some as payload:
417
+ request.append_span(payload.value)
418
+
419
+ return Result[vec.Vec[ubyte], Error].success(value = request)
420
+
421
+
422
+ function find_crlf(text_value: str, start: ptr_uint) -> Option[ptr_uint]:
423
+ if text_value.len < 2 or start + 1 >= text_value.len:
424
+ return Option[ptr_uint].none
425
+
426
+ var index = start
427
+ while index + 1 < text_value.len:
428
+ if text_value.byte_at(index) == 13 and text_value.byte_at(index + 1) == 10:
429
+ return Option[ptr_uint].some(value = index)
430
+ index += 1
431
+
432
+ return Option[ptr_uint].none
433
+
434
+
435
+ function find_header_terminator(data: span[ubyte]) -> Option[ptr_uint]:
436
+ if data.len < 4:
437
+ return Option[ptr_uint].none
438
+
439
+ var index: ptr_uint = 0
440
+ while index + 3 < data.len:
441
+ if (
442
+ unsafe: read(data.data + index) == 13
443
+ and read(data.data + index + 1) == 10
444
+ and read(data.data + index + 2) == 13
445
+ and read(data.data + index + 3) == 10
446
+ ):
447
+ return Option[ptr_uint].some(value = index)
448
+ index += 1
449
+
450
+ return Option[ptr_uint].none
451
+
452
+
453
+ function find_crlf_bytes(data: span[ubyte], start: ptr_uint) -> Option[ptr_uint]:
454
+ if data.len < 2 or start + 1 >= data.len:
455
+ return Option[ptr_uint].none
456
+
457
+ var index = start
458
+ while index + 1 < data.len:
459
+ if unsafe: read(data.data + index) == 13 and read(data.data + index + 1) == 10:
460
+ return Option[ptr_uint].some(value = index)
461
+ index += 1
462
+
463
+ return Option[ptr_uint].none
464
+
465
+
466
+ function hexadecimal_digit_value(current: ubyte) -> Option[ptr_uint]:
467
+ if current < 48:
468
+ return Option[ptr_uint].none
469
+
470
+ if current <= 57:
471
+ return Option[ptr_uint].some(value = ptr_uint<-(current - 48ub))
472
+
473
+ if current < 65:
474
+ return Option[ptr_uint].none
475
+
476
+ if current <= 70:
477
+ return Option[ptr_uint].some(value = ptr_uint<-(current - 55ub))
478
+
479
+ if current < 97:
480
+ return Option[ptr_uint].none
481
+
482
+ if current <= 102:
483
+ return Option[ptr_uint].some(value = ptr_uint<-(current - 87ub))
484
+
485
+ return Option[ptr_uint].none
486
+
487
+
488
+ function parse_hexadecimal(text_value: str) -> Option[ptr_uint]:
489
+ if text_value.len == 0:
490
+ return Option[ptr_uint].none
491
+
492
+ var value: ptr_uint = 0
493
+ var index: ptr_uint = 0
494
+ while index < text_value.len:
495
+ let current = text_value.byte_at(index)
496
+ let digit = hexadecimal_digit_value(current) else:
497
+ return Option[ptr_uint].none
498
+
499
+ if value > (heap.ptr_uint_max - digit) / 16:
500
+ return Option[ptr_uint].none
501
+
502
+ value = value * 16 + digit
503
+ index += 1
504
+
505
+ return Option[ptr_uint].some(value = value)
506
+
507
+
508
+ function parse_chunk_size(text_value: str) -> Option[ptr_uint]:
509
+ var size_text = text_value
510
+ let extension = find_byte_from(text_value, 59, 0) else:
511
+ let parsed = parse_hexadecimal(size_text.trim_ascii_whitespace()) else:
512
+ return Option[ptr_uint].none
513
+ return Option[ptr_uint].some(value = parsed)
514
+
515
+ size_text = text_value.slice(0, extension)
516
+ let parsed = parse_hexadecimal(size_text.trim_ascii_whitespace()) else:
517
+ return Option[ptr_uint].none
518
+ return Option[ptr_uint].some(value = parsed)
519
+
520
+
521
+ function discard_buffer_prefix(buffer: ref[vec.Vec[ubyte]], count: ptr_uint) -> void:
522
+ if count == 0:
523
+ return
524
+
525
+ if count >= buffer.len:
526
+ buffer.clear()
527
+ return
528
+
529
+ let data = buffer.data else:
530
+ fatal(c"http.discard_buffer_prefix missing storage")
531
+
532
+ let remaining = buffer.len - count
533
+ unsafe:
534
+ let data_ptr = data
535
+ var index: ptr_uint = 0
536
+ while index < remaining:
537
+ read(data_ptr + index) = read(data_ptr + count + index)
538
+ index += 1
539
+
540
+ buffer.len = remaining
541
+
542
+
543
+ function parse_response_head(header_text: str) -> Result[ResponseHead, Error]:
544
+ var line_end = header_text.len
545
+ var header_index = header_text.len
546
+ match find_crlf(header_text, 0):
547
+ Option.none:
548
+ pass
549
+ Option.some as payload:
550
+ line_end = payload.value
551
+ header_index = line_end + 2
552
+
553
+ let status_line = header_text.slice(0, line_end)
554
+ if not status_line.starts_with("HTTP/1."):
555
+ return Result[ResponseHead, Error].failure(error = response_error("unsupported HTTP version"))
556
+
557
+ var first_space: ptr_uint = 0
558
+ match status_line.find_byte(32):
559
+ Option.none:
560
+ return Result[ResponseHead, Error].failure(error = response_error("missing status code"))
561
+ Option.some as payload:
562
+ first_space = payload.value
563
+
564
+ if first_space + 4 > status_line.len:
565
+ return Result[ResponseHead, Error].failure(error = response_error("missing status code"))
566
+
567
+ let status_code_text = status_line.slice(first_space + 1, 3)
568
+ var status_code = 0
569
+ match parse_decimal(status_code_text):
570
+ Option.none:
571
+ return Result[ResponseHead, Error].failure(error = response_error("invalid status code"))
572
+ Option.some as payload:
573
+ status_code = int<-payload.value
574
+
575
+ var reason = string.String.create()
576
+ if first_space + 4 < status_line.len:
577
+ if status_line.byte_at(first_space + 4) != 32:
578
+ return Result[
579
+ ResponseHead,
580
+ Error
581
+ ].failure(error = response_error("status line must separate code and reason with a space"))
582
+
583
+ let reason_start = first_space + 5
584
+ reason = string.String.from_str(status_line.slice(reason_start, status_line.len - reason_start))
585
+
586
+ var head = ResponseHead(
587
+ status_code = status_code,
588
+ reason = reason,
589
+ headers = vec.Vec[Header].create(),
590
+ content_length = Option[ptr_uint].none,
591
+ chunked = false
592
+ )
593
+
594
+ var index = header_index
595
+ while index < header_text.len:
596
+ var next_line_end = header_text.len
597
+ match find_crlf(header_text, index):
598
+ Option.none:
599
+ pass
600
+ Option.some as payload:
601
+ next_line_end = payload.value
602
+
603
+ let line = header_text.slice(index, next_line_end - index)
604
+ if line.len == 0:
605
+ head.release()
606
+ return Result[ResponseHead, Error].failure(error = response_error("unexpected blank header line"))
607
+
608
+ var separator: ptr_uint = 0
609
+ match line.find_byte(58):
610
+ Option.none:
611
+ head.release()
612
+ return Result[ResponseHead, Error].failure(error = response_error("header line is missing ':'"))
613
+ Option.some as payload:
614
+ separator = payload.value
615
+
616
+ if separator == 0:
617
+ head.release()
618
+ return Result[ResponseHead, Error].failure(error = response_error("header name is empty"))
619
+
620
+ let name_text = line.slice(0, separator)
621
+ let value_start = separator + 1
622
+ let value_text = line.slice(value_start, line.len - value_start).trim_ascii_whitespace()
623
+
624
+ head.headers.push(Header(name = string.String.from_str(name_text), value = string.String.from_str(value_text)))
625
+
626
+ if ascii_case_equal(name_text, "Content-Length"):
627
+ match parse_decimal(value_text):
628
+ Option.none:
629
+ head.release()
630
+ return Result[
631
+ ResponseHead,
632
+ Error
633
+ ].failure(error = response_error("Content-Length must be a decimal integer"))
634
+ Option.some as payload:
635
+ head.content_length = Option[ptr_uint].some(value = payload.value)
636
+
637
+ if ascii_case_equal(name_text, "Transfer-Encoding"):
638
+ if ascii_case_equal(value_text, "chunked"):
639
+ head.chunked = true
640
+ else:
641
+ head.release()
642
+ return Result[ResponseHead, Error].failure(error = response_error("unsupported Transfer-Encoding"))
643
+
644
+ if next_line_end == header_text.len:
645
+ index = header_text.len
646
+ else:
647
+ index = next_line_end + 2
648
+
649
+ if head.chunked:
650
+ head.content_length = Option[ptr_uint].none
651
+
652
+ return Result[ResponseHead, Error].success(value = head)
653
+
654
+
655
+ function decode_buffered_chunked_body(encoded: span[ubyte]) -> Result[bytes.Bytes, Error]:
656
+ var body = vec.Vec[ubyte].with_capacity(encoded.len)
657
+ defer body.release()
658
+
659
+ var cursor: ptr_uint = 0
660
+ while true:
661
+ let line_end = find_crlf_bytes(encoded, cursor) else:
662
+ return Result[
663
+ bytes.Bytes,
664
+ Error
665
+ ].failure(error = response_error("chunked response ended before chunk size"))
666
+
667
+ let line_bytes = unsafe: span[ubyte](data = encoded.data + cursor, len = line_end - cursor)
668
+ let line_text = text.utf8_byte_span_as_str(line_bytes) else:
669
+ return Result[bytes.Bytes, Error].failure(error = response_error("chunk size line is not valid UTF-8"))
670
+
671
+ let chunk_size = parse_chunk_size(line_text) else:
672
+ return Result[bytes.Bytes, Error].failure(error = response_error("invalid chunk size"))
673
+
674
+ cursor = line_end + 2
675
+
676
+ if chunk_size == 0:
677
+ while true:
678
+ let trailer_end = find_crlf_bytes(encoded, cursor) else:
679
+ return Result[
680
+ bytes.Bytes,
681
+ Error
682
+ ].failure(error = response_error("chunked response ended before trailers were complete"))
683
+
684
+ if trailer_end == cursor:
685
+ if trailer_end + 2 != encoded.len:
686
+ return Result[
687
+ bytes.Bytes,
688
+ Error
689
+ ].failure(error = response_error("unexpected bytes after chunked body"))
690
+
691
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body.as_span()))
692
+
693
+ cursor = trailer_end + 2
694
+
695
+ if encoded.len < cursor or encoded.len - cursor < chunk_size:
696
+ return Result[
697
+ bytes.Bytes,
698
+ Error
699
+ ].failure(error = response_error("chunked response ended before chunk data"))
700
+
701
+ let remaining = encoded.len - cursor - chunk_size
702
+ if remaining < 2:
703
+ return Result[
704
+ bytes.Bytes,
705
+ Error
706
+ ].failure(error = response_error("chunked response ended before chunk terminator"))
707
+
708
+ let chunk_bytes = unsafe: span[ubyte](data = encoded.data + cursor, len = chunk_size)
709
+ body.append_span(chunk_bytes)
710
+ cursor += chunk_size
711
+
712
+ if unsafe: read(encoded.data + cursor) != 13 or read(encoded.data + cursor + 1) != 10:
713
+ return Result[bytes.Bytes, Error].failure(error = response_error("chunk data must end with CRLF"))
714
+
715
+ cursor += 2
716
+
717
+
718
+ function parse_buffered_body(
719
+ body_bytes: span[ubyte],
720
+ content_length: Option[ptr_uint],
721
+ chunked: bool
722
+ ) -> Result[bytes.Bytes, Error]:
723
+ if chunked:
724
+ return decode_buffered_chunked_body(body_bytes)
725
+
726
+ match content_length:
727
+ Option.none:
728
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body_bytes))
729
+ Option.some as payload:
730
+ if body_bytes.len != payload.value:
731
+ return Result[
732
+ bytes.Bytes,
733
+ Error
734
+ ].failure(error = response_error("body length did not match Content-Length"))
735
+
736
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body_bytes))
737
+
738
+
739
+ function parse_buffered_response(raw_response: span[ubyte]) -> Result[Response, Error]:
740
+ let header_length = find_header_terminator(raw_response) else:
741
+ return Result[Response, Error].failure(error = response_error("response ended before headers were complete"))
742
+
743
+ let header_bytes = span[ubyte](data = raw_response.data, len = header_length)
744
+ let header_text = text.utf8_byte_span_as_str(header_bytes) else:
745
+ return Result[Response, Error].failure(error = response_error("headers are not valid UTF-8"))
746
+
747
+ let head_result = parse_response_head(header_text)
748
+ match head_result:
749
+ Result.failure as payload:
750
+ return Result[Response, Error].failure(error = payload.error)
751
+ Result.success as payload:
752
+ var head = payload.value
753
+ let body_start = header_length + 4
754
+ let body_bytes = unsafe: span[ubyte](
755
+ data = raw_response.data + body_start,
756
+ len = raw_response.len - body_start
757
+ )
758
+
759
+ let body_result = parse_buffered_body(body_bytes, head.content_length, head.chunked)
760
+ match body_result:
761
+ Result.failure as body_payload:
762
+ head.release()
763
+ return Result[Response, Error].failure(error = body_payload.error)
764
+ Result.success as body_payload:
765
+ let body = body_payload.value
766
+ return Result[Response, Error].success(
767
+ value = Response(
768
+ status_code = head.status_code,
769
+ reason = head.reason,
770
+ headers = head.headers,
771
+ body = body
772
+ )
773
+ )
774
+
775
+
776
+ async function read_chunked_body(stream: net.TcpStream, prefix: span[ubyte]) -> Result[bytes.Bytes, Error]:
777
+ var body = vec.Vec[ubyte].with_capacity(prefix.len)
778
+ defer body.release()
779
+
780
+ var buffer = vec.Vec[ubyte].with_capacity(prefix.len + 64)
781
+ defer buffer.release()
782
+ buffer.append_span(prefix)
783
+
784
+ var cursor: ptr_uint = 0
785
+ while true:
786
+ var line_end: ptr_uint = 0
787
+ while true:
788
+ match find_crlf_bytes(buffer.as_span(), cursor):
789
+ Option.none:
790
+ if cursor > 0:
791
+ discard_buffer_prefix(ref_of(buffer), cursor)
792
+ cursor = 0
793
+
794
+ let chunk_result = await stream.read_once(4096)
795
+ match chunk_result:
796
+ Result.failure as error_payload:
797
+ return Result[bytes.Bytes, Error].failure(error = status_net_error(error_payload.error))
798
+ Result.success as ok_payload:
799
+ var chunk = ok_payload.value
800
+ if chunk.len == 0:
801
+ chunk.release()
802
+ return Result[
803
+ bytes.Bytes,
804
+ Error
805
+ ].failure(error = response_error("chunked response ended before chunk size"))
806
+
807
+ buffer.append_span(chunk.as_span())
808
+ chunk.release()
809
+ continue
810
+ Option.some as payload:
811
+ line_end = payload.value
812
+ break
813
+
814
+ let line_bytes = unsafe: span[ubyte](data = buffer.as_span().data + cursor, len = line_end - cursor)
815
+ let line_text = text.utf8_byte_span_as_str(line_bytes) else:
816
+ return Result[bytes.Bytes, Error].failure(error = response_error("chunk size line is not valid UTF-8"))
817
+
818
+ let chunk_size = parse_chunk_size(line_text) else:
819
+ return Result[bytes.Bytes, Error].failure(error = response_error("invalid chunk size"))
820
+
821
+ cursor = line_end + 2
822
+
823
+ if chunk_size == 0:
824
+ while true:
825
+ var trailer_end: ptr_uint = 0
826
+ while true:
827
+ match find_crlf_bytes(buffer.as_span(), cursor):
828
+ Option.none:
829
+ if cursor > 0:
830
+ discard_buffer_prefix(ref_of(buffer), cursor)
831
+ cursor = 0
832
+
833
+ let chunk_result = await stream.read_once(4096)
834
+ match chunk_result:
835
+ Result.failure as error_payload:
836
+ return Result[
837
+ bytes.Bytes,
838
+ Error
839
+ ].failure(error = status_net_error(error_payload.error))
840
+ Result.success as ok_payload:
841
+ var chunk = ok_payload.value
842
+ if chunk.len == 0:
843
+ chunk.release()
844
+ return Result[
845
+ bytes.Bytes,
846
+ Error
847
+ ].failure(error = response_error("chunked response ended before trailers were complete"))
848
+
849
+ buffer.append_span(chunk.as_span())
850
+ chunk.release()
851
+ continue
852
+ Option.some as payload:
853
+ trailer_end = payload.value
854
+ break
855
+
856
+ if trailer_end == cursor:
857
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body.as_span()))
858
+
859
+ cursor = trailer_end + 2
860
+
861
+ while buffer.len - cursor < chunk_size + 2:
862
+ if cursor > 0:
863
+ discard_buffer_prefix(ref_of(buffer), cursor)
864
+ cursor = 0
865
+
866
+ let chunk_result = await stream.read_once(4096)
867
+ match chunk_result:
868
+ Result.failure as error_payload:
869
+ return Result[bytes.Bytes, Error].failure(error = status_net_error(error_payload.error))
870
+ Result.success as ok_payload:
871
+ var chunk = ok_payload.value
872
+ if chunk.len == 0:
873
+ chunk.release()
874
+ return Result[
875
+ bytes.Bytes,
876
+ Error
877
+ ].failure(error = response_error("chunked response ended before chunk data"))
878
+
879
+ buffer.append_span(chunk.as_span())
880
+ chunk.release()
881
+
882
+ let chunk_bytes = unsafe: span[ubyte](data = buffer.as_span().data + cursor, len = chunk_size)
883
+ body.append_span(chunk_bytes)
884
+ cursor += chunk_size
885
+
886
+ let unread = buffer.as_span()
887
+ if unsafe: read(unread.data + cursor) != 13 or read(unread.data + cursor + 1) != 10:
888
+ return Result[bytes.Bytes, Error].failure(error = response_error("chunk data must end with CRLF"))
889
+
890
+ cursor += 2
891
+
892
+
893
+ async function read_body(
894
+ stream: net.TcpStream,
895
+ prefix: span[ubyte],
896
+ content_length: Option[ptr_uint],
897
+ chunked: bool
898
+ ) -> Result[bytes.Bytes, Error]:
899
+ if chunked:
900
+ return await read_chunked_body(stream, prefix)
901
+
902
+ match content_length:
903
+ Option.some as payload:
904
+ let total_length = payload.value
905
+ if prefix.len > total_length:
906
+ return Result[bytes.Bytes, Error].failure(error = response_error("body exceeded Content-Length"))
907
+
908
+ var body = vec.Vec[ubyte].with_capacity(total_length)
909
+ defer body.release()
910
+
911
+ body.append_span(prefix)
912
+
913
+ let remaining = total_length - prefix.len
914
+ if remaining > 0:
915
+ let chunk_result = await stream.read_exactly(remaining)
916
+ match chunk_result:
917
+ Result.failure as error_payload:
918
+ return Result[bytes.Bytes, Error].failure(error = status_net_error(error_payload.error))
919
+ Result.success as ok_payload:
920
+ var chunk = ok_payload.value
921
+ body.append_span(chunk.as_span())
922
+ chunk.release()
923
+
924
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body.as_span()))
925
+ Option.none:
926
+ var body = vec.Vec[ubyte].with_capacity(prefix.len)
927
+ defer body.release()
928
+
929
+ body.append_span(prefix)
930
+
931
+ while true:
932
+ let chunk_result = await stream.read_once(4096)
933
+ match chunk_result:
934
+ Result.failure as error_payload:
935
+ return Result[bytes.Bytes, Error].failure(error = status_net_error(error_payload.error))
936
+ Result.success as ok_payload:
937
+ var chunk = ok_payload.value
938
+ if chunk.len == 0:
939
+ chunk.release()
940
+ break
941
+
942
+ body.append_span(chunk.as_span())
943
+ chunk.release()
944
+
945
+ return Result[bytes.Bytes, Error].success(value = bytes.Bytes.copy(body.as_span()))
946
+
947
+
948
+ async function read_response(stream: net.TcpStream) -> Result[Response, Error]:
949
+ var received = vec.Vec[ubyte].create()
950
+ defer received.release()
951
+
952
+ var header_length: ptr_uint = 0
953
+ var headers_ready = false
954
+ while not headers_ready:
955
+ let chunk_result = await stream.read_once(4096)
956
+ match chunk_result:
957
+ Result.failure as error_payload:
958
+ return Result[Response, Error].failure(error = status_net_error(error_payload.error))
959
+ Result.success as ok_payload:
960
+ var chunk = ok_payload.value
961
+ if chunk.len == 0:
962
+ chunk.release()
963
+ return Result[
964
+ Response,
965
+ Error
966
+ ].failure(error = response_error("response ended before headers were complete"))
967
+
968
+ received.append_span(chunk.as_span())
969
+ chunk.release()
970
+
971
+ match find_header_terminator(received.as_span()):
972
+ Option.none:
973
+ continue
974
+ Option.some as payload:
975
+ header_length = payload.value
976
+ headers_ready = true
977
+
978
+ let raw = received.as_span()
979
+ let header_bytes = span[ubyte](data = raw.data, len = header_length)
980
+ let header_text = text.utf8_byte_span_as_str(header_bytes) else:
981
+ return Result[Response, Error].failure(error = response_error("headers are not valid UTF-8"))
982
+
983
+ let head_result = parse_response_head(header_text)
984
+ match head_result:
985
+ Result.failure as payload:
986
+ return Result[Response, Error].failure(error = payload.error)
987
+ Result.success as payload:
988
+ var head = payload.value
989
+ let body_start = header_length + 4
990
+ let body_prefix = unsafe: span[ubyte](data = raw.data + body_start, len = raw.len - body_start)
991
+
992
+ let body_result = await read_body(stream, body_prefix, head.content_length, head.chunked)
993
+ match body_result:
994
+ Result.failure as body_error_payload:
995
+ head.release()
996
+ return Result[Response, Error].failure(error = body_error_payload.error)
997
+ Result.success as body_payload:
998
+ let body = body_payload.value
999
+ return Result[Response, Error].success(
1000
+ value = Response(
1001
+ status_code = head.status_code,
1002
+ reason = head.reason,
1003
+ headers = head.headers,
1004
+ body = body
1005
+ )
1006
+ )
1007
+
1008
+
1009
+ async function request_http(parsed: ParsedUrl, request_bytes: span[ubyte]) -> Result[Response, Error]:
1010
+ var service = fmt.to_string_int(parsed.port)
1011
+ defer service.release()
1012
+
1013
+ let address_result = await net.resolve_first(parsed.host.as_str(), service.as_str())
1014
+ match address_result:
1015
+ Result.failure as address_error_payload:
1016
+ return Result[Response, Error].failure(error = status_net_error(address_error_payload.error))
1017
+ Result.success as address_payload:
1018
+ var address = address_payload.value
1019
+ defer address.release()
1020
+
1021
+ let connect_result = await net.connect(address)
1022
+ match connect_result:
1023
+ Result.failure as connect_error_payload:
1024
+ return Result[Response, Error].failure(error = status_net_error(connect_error_payload.error))
1025
+ Result.success as connect_payload:
1026
+ var stream = connect_payload.value
1027
+ defer stream.release()
1028
+
1029
+ let write_result = await stream.write_bytes(request_bytes)
1030
+ match write_result:
1031
+ Result.failure as write_error_payload:
1032
+ return Result[Response, Error].failure(error = status_net_error(write_error_payload.error))
1033
+ Result.success as write_payload:
1034
+ if write_payload.value != request_bytes.len:
1035
+ return Result[
1036
+ Response,
1037
+ Error
1038
+ ].failure(error = status_error("http request write did not send the full request"))
1039
+
1040
+ return await read_response(stream)
1041
+
1042
+
1043
+ async function read_buffered_tls_response(stream: tls.Stream) -> Result[Response, Error]:
1044
+ var received = vec.Vec[ubyte].create()
1045
+ defer received.release()
1046
+
1047
+ while true:
1048
+ let chunk_result = await stream.read_once(4096)
1049
+ match chunk_result:
1050
+ Result.failure as error_payload:
1051
+ return Result[Response, Error].failure(error = status_tls_error(error_payload.error))
1052
+ Result.success as chunk_payload:
1053
+ var chunk = chunk_payload.value
1054
+ if chunk.len == 0:
1055
+ chunk.release()
1056
+ break
1057
+
1058
+ received.append_span(chunk.as_span())
1059
+ chunk.release()
1060
+
1061
+ return parse_buffered_response(received.as_span())
1062
+
1063
+
1064
+ async function request_https(parsed: ParsedUrl, request_bytes: span[ubyte]) -> Result[Response, Error]:
1065
+ let connect_result = await tls.connect(parsed.host.as_str(), parsed.port)
1066
+ match connect_result:
1067
+ Result.failure as payload:
1068
+ return Result[Response, Error].failure(error = status_tls_error(payload.error))
1069
+ Result.success as payload:
1070
+ var stream = payload.value
1071
+ defer stream.release()
1072
+
1073
+ let write_result = await stream.write_bytes(request_bytes)
1074
+ match write_result:
1075
+ Result.failure as write_error_payload:
1076
+ return Result[Response, Error].failure(error = status_tls_error(write_error_payload.error))
1077
+ Result.success as write_payload:
1078
+ if write_payload.value != request_bytes.len:
1079
+ return Result[
1080
+ Response,
1081
+ Error
1082
+ ].failure(error = status_error("https request write did not send the full request"))
1083
+
1084
+ return await read_buffered_tls_response(stream)
1085
+
1086
+
1087
+ async function request_with_transport(parsed: ParsedUrl, request_bytes: span[ubyte]) -> Result[Response, Error]:
1088
+ match parsed.scheme:
1089
+ UrlScheme.http:
1090
+ return await request_http(parsed, request_bytes)
1091
+ UrlScheme.https:
1092
+ return await request_https(parsed, request_bytes)
1093
+
1094
+
1095
+ extending Error:
1096
+ public editable function release() -> void:
1097
+ this.message.release()
1098
+
1099
+
1100
+ extending Header:
1101
+ public editable function release() -> void:
1102
+ this.name.release()
1103
+ this.value.release()
1104
+
1105
+
1106
+ extending Response:
1107
+ public editable function release() -> void:
1108
+ this.reason.release()
1109
+
1110
+ var index: ptr_uint = 0
1111
+ while index < this.headers.len:
1112
+ let current = this.headers.get(index) else:
1113
+ fatal(c"http.Response.release missing header")
1114
+
1115
+ var header = unsafe: read(current)
1116
+ header.release()
1117
+ index += 1
1118
+
1119
+ this.headers.release()
1120
+ this.body.release()
1121
+
1122
+
1123
+ public function body_as_str() -> Option[str]:
1124
+ return this.body.as_str()
1125
+
1126
+
1127
+ public function header(name: str) -> Option[str]:
1128
+ var index: ptr_uint = 0
1129
+ while index < this.headers.len:
1130
+ let current = this.headers.get(index) else:
1131
+ fatal(c"http.Response.header missing header")
1132
+
1133
+ let header_value = unsafe: read(current)
1134
+ if ascii_case_equal(header_value.name.as_str(), name):
1135
+ return Option[str].some(value = header_value.value.as_str())
1136
+
1137
+ index += 1
1138
+
1139
+ return Option[str].none
1140
+
1141
+
1142
+ extending ParsedUrl:
1143
+ editable function release() -> void:
1144
+ this.host.release()
1145
+ this.authority.release()
1146
+ this.target.release()
1147
+
1148
+
1149
+ extending ResponseHead:
1150
+ editable function release() -> void:
1151
+ this.reason.release()
1152
+
1153
+ var index: ptr_uint = 0
1154
+ while index < this.headers.len:
1155
+ let current = this.headers.get(index) else:
1156
+ fatal(c"http.ResponseHead.release missing header")
1157
+
1158
+ var header = unsafe: read(current)
1159
+ header.release()
1160
+ index += 1
1161
+
1162
+ this.headers.release()
1163
+
1164
+
1165
+ public async function get(url: str) -> Result[Response, Error]:
1166
+ return await request(url, "GET", zero[span[RequestHeader]], Option[span[ubyte]].none)
1167
+
1168
+
1169
+ public async function head(url: str) -> Result[Response, Error]:
1170
+ return await request(url, "HEAD", zero[span[RequestHeader]], Option[span[ubyte]].none)
1171
+
1172
+
1173
+ public async function delete(url: str) -> Result[Response, Error]:
1174
+ return await request(url, "DELETE", zero[span[RequestHeader]], Option[span[ubyte]].none)
1175
+
1176
+
1177
+ public async function options(url: str) -> Result[Response, Error]:
1178
+ return await request(url, "OPTIONS", zero[span[RequestHeader]], Option[span[ubyte]].none)
1179
+
1180
+
1181
+ async function request_with_body(
1182
+ url: str,
1183
+ method: str,
1184
+ body: str,
1185
+ content_type: str
1186
+ ) -> Result[Response, Error]:
1187
+ var ct = RequestHeader(name = "Content-Type", value = content_type)
1188
+ let headers = span[RequestHeader](data = ptr_of(ct), len = 1)
1189
+ let body_bytes = text.as_byte_span(body)
1190
+ return await request(url, method, headers, Option[span[ubyte]].some(value = body_bytes))
1191
+
1192
+
1193
+ public async function post(url: str, body: str, content_type: str) -> Result[Response, Error]:
1194
+ return await request_with_body(url, "POST", body, content_type)
1195
+
1196
+
1197
+ public async function put(url: str, body: str, content_type: str) -> Result[Response, Error]:
1198
+ return await request_with_body(url, "PUT", body, content_type)
1199
+
1200
+
1201
+ public async function patch(url: str, body: str, content_type: str) -> Result[Response, Error]:
1202
+ return await request_with_body(url, "PATCH", body, content_type)
1203
+
1204
+
1205
+ public async function request(
1206
+ url: str,
1207
+ method: str,
1208
+ headers: span[RequestHeader],
1209
+ body: Option[span[ubyte]]
1210
+ ) -> Result[Response, Error]:
1211
+ let parsed_result = parse_url(url)
1212
+ match parsed_result:
1213
+ Result.failure as payload:
1214
+ return Result[Response, Error].failure(error = payload.error)
1215
+ Result.success as payload:
1216
+ var parsed = payload.value
1217
+ defer parsed.release()
1218
+
1219
+ let request_result = build_request(parsed, method, headers, body)
1220
+ match request_result:
1221
+ Result.failure as request_error_payload:
1222
+ return Result[Response, Error].failure(error = request_error_payload.error)
1223
+ Result.success as request_payload:
1224
+ var request_bytes = request_payload.value
1225
+ defer request_bytes.release()
1226
+
1227
+ return await request_with_transport(parsed, request_bytes.as_span())