MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdbool.h>
4#include <stdint.h>
5#include <stdlib.h>
6#include <string.h>
7#include <strings.h>
8
9#include "ant.h"
10#include "errors.h"
11#include "internal.h"
12
13#include "http/http1_writer.h"
14#include "modules/buffer.h"
15#include "modules/http_writer.h"
16
17static ant_value_t http_writer_make_buffer_value(ant_t *js, ant_http1_buffer_t *buf) {
18 ant_value_t out = 0;
19 char *data = NULL;
20 size_t len = 0;
21 ArrayBufferData *ab = NULL;
22
23 data = ant_http1_buffer_take(buf, &len);
24 ab = create_array_buffer_data(len);
25 if (!ab) {
26 free(data);
27 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
28 }
29
30 if (len > 0 && data) memcpy(ab->data, data, len);
31 free(data);
32 out = create_typed_array(js, TYPED_ARRAY_UINT8, ab, 0, len, "Buffer");
33 return out;
34}
35
36static bool http_writer_append_raw_headers(
37 ant_t *js,
38 ant_http1_buffer_t *buf,
39 ant_value_t raw_headers,
40 ant_value_t *error_out
41) {
42 ant_offset_t len = 0;
43
44 if (error_out) *error_out = js_mkundef();
45 if (vtype(raw_headers) == T_UNDEF || vtype(raw_headers) == T_NULL) return true;
46 if (vtype(raw_headers) != T_ARR) {
47 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "rawHeaders must be an array");
48 return false;
49 }
50
51 len = js_arr_len(js, raw_headers);
52 if ((len & 1) != 0) {
53 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "rawHeaders must contain name/value pairs");
54 return false;
55 }
56
57 for (ant_offset_t i = 0; i < len; i += 2) {
58 ant_value_t name_value = js_tostring_val(js, js_arr_get(js, raw_headers, i));
59 ant_value_t header_value = js_tostring_val(js, js_arr_get(js, raw_headers, i + 1));
60 const char *name = NULL;
61 const char *value = NULL;
62 size_t name_len = 0;
63 size_t value_len = 0;
64
65 if (is_err(name_value)) {
66 if (error_out) *error_out = name_value;
67 return false;
68 }
69 if (is_err(header_value)) {
70 if (error_out) *error_out = header_value;
71 return false;
72 }
73
74 name = js_getstr(js, name_value, &name_len);
75 value = js_getstr(js, header_value, &value_len);
76 if (!name || !value) {
77 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid raw header entry");
78 return false;
79 }
80
81 if (strcasecmp(name, "connection") == 0) continue;
82 if (strcasecmp(name, "content-length") == 0) continue;
83 if (strcasecmp(name, "transfer-encoding") == 0) continue;
84 if (!ant_http1_buffer_appendf(buf, "%s: %s\r\n", name, value)) break;
85 }
86
87 if (buf->failed && error_out && vtype(*error_out) == T_UNDEF)
88 *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
89 return !buf->failed;
90}
91
92static bool http_writer_parse_bytes(
93 ant_t *js,
94 ant_value_t value,
95 const uint8_t **bytes_out,
96 size_t *len_out,
97 ant_value_t *error_out
98) {
99 ant_value_t str_value = 0;
100 const char *str = NULL;
101 size_t len = 0;
102
103 if (error_out) *error_out = js_mkundef();
104 if (bytes_out) *bytes_out = NULL;
105 if (len_out) *len_out = 0;
106
107 if (vtype(value) == T_UNDEF || vtype(value) == T_NULL) return true;
108 if (buffer_source_get_bytes(js, value, bytes_out, len_out)) return true;
109
110 str_value = js_tostring_val(js, value);
111 if (is_err(str_value)) {
112 if (error_out) *error_out = str_value;
113 return false;
114 }
115
116 str = js_getstr(js, str_value, &len);
117 if (!str) {
118 if (error_out) *error_out = js_mkerr_typed(js, JS_ERR_TYPE, "Invalid HTTP body chunk");
119 return false;
120 }
121
122 if (bytes_out) *bytes_out = (const uint8_t *)str;
123 if (len_out) *len_out = len;
124 return true;
125}
126
127static ant_value_t js_http_writer_default_status_text(ant_t *js, ant_value_t *args, int nargs) {
128 int status = nargs > 0 ? (int)js_getnum(args[0]) : 200;
129 const char *text = ant_http1_default_status_text(status);
130 return js_mkstr(js, text, strlen(text));
131}
132
133static ant_value_t js_http_writer_write_chunk(ant_t *js, ant_value_t *args, int nargs) {
134 ant_http1_buffer_t buf;
135 const uint8_t *bytes = NULL;
136 size_t len = 0;
137 ant_value_t error = js_mkundef();
138
139 ant_http1_buffer_init(&buf);
140 if (!http_writer_parse_bytes(js, nargs > 0 ? args[0] : js_mkundef(), &bytes, &len, &error)) return error;
141 if (!ant_http1_write_chunk(&buf, bytes, len)) {
142 ant_http1_buffer_free(&buf);
143 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
144 }
145
146 return http_writer_make_buffer_value(js, &buf);
147}
148
149static ant_value_t js_http_writer_write_final_chunk(ant_t *js, ant_value_t *args, int nargs) {
150 ant_http1_buffer_t buf;
151
152 ant_http1_buffer_init(&buf);
153 if (!ant_http1_write_final_chunk(&buf)) {
154 ant_http1_buffer_free(&buf);
155 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
156 }
157
158 return http_writer_make_buffer_value(js, &buf);
159}
160
161static ant_value_t js_http_writer_write_basic_response(ant_t *js, ant_value_t *args, int nargs) {
162 ant_http1_buffer_t buf;
163 ant_value_t error = js_mkundef();
164 const uint8_t *body = NULL;
165
166 size_t body_len = 0;
167 int status = nargs > 0 ? (int)js_getnum(args[0]) : 200;
168
169 const char *status_text = NULL;
170 const char *content_type = NULL;
171 bool keep_alive = false;
172
173 if (nargs > 1 && vtype(args[1]) != T_UNDEF && vtype(args[1]) != T_NULL) {
174 ant_value_t status_text_value = js_tostring_val(js, args[1]);
175 if (is_err(status_text_value)) return status_text_value;
176 status_text = js_getstr(js, status_text_value, NULL);
177 }
178
179 if (nargs > 2 && vtype(args[2]) != T_UNDEF && vtype(args[2]) != T_NULL) {
180 ant_value_t content_type_value = js_tostring_val(js, args[2]);
181 if (is_err(content_type_value)) return content_type_value;
182 content_type = js_getstr(js, content_type_value, NULL);
183 }
184
185 if (!http_writer_parse_bytes(js, nargs > 3 ? args[3] : js_mkundef(), &body, &body_len, &error)) return error;
186 keep_alive = nargs > 4 && js_truthy(js, args[4]);
187
188 ant_http1_buffer_init(&buf);
189 if (!ant_http1_write_basic_response(&buf, status, status_text, content_type, body, body_len, keep_alive)) {
190 ant_http1_buffer_free(&buf);
191 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
192 }
193
194 return http_writer_make_buffer_value(js, &buf);
195}
196
197static ant_value_t js_http_writer_write_head(ant_t *js, ant_value_t *args, int nargs) {
198 ant_http1_buffer_t buf;
199 ant_value_t error = js_mkundef();
200
201 int status = nargs > 0 ? (int)js_getnum(args[0]) : 200;
202 const char *status_text = NULL;
203 ant_value_t raw_headers = js_mkundef();
204
205 bool body_is_stream = false;
206 size_t body_size = 0;
207 bool keep_alive = false;
208 int index = 1;
209
210 if (nargs > index && vtype(args[index]) != T_UNDEF && vtype(args[index]) != T_NULL && vtype(args[index]) != T_ARR) {
211 ant_value_t status_text_value = js_tostring_val(js, args[index]);
212 if (is_err(status_text_value)) return status_text_value;
213 status_text = js_getstr(js, status_text_value, NULL);
214 index++;
215 }
216
217 if (nargs > index) raw_headers = args[index++];
218 if (nargs > index) body_is_stream = js_truthy(js, args[index++]);
219 if (nargs > index) body_size = (size_t)js_getnum(args[index++]);
220 if (nargs > index) keep_alive = js_truthy(js, args[index++]);
221
222 ant_http1_buffer_init(&buf);
223 if (!ant_http1_buffer_appendf(&buf, "HTTP/1.1 %d %s\r\n", status, status_text ? status_text : ant_http1_default_status_text(status))) {
224 ant_http1_buffer_free(&buf);
225 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
226 }
227
228 if (!http_writer_append_raw_headers(js, &buf, raw_headers, &error)) {
229 ant_http1_buffer_free(&buf);
230 return error;
231 }
232
233 if (body_is_stream) ant_http1_buffer_append_cstr(&buf, "Transfer-Encoding: chunked\r\n");
234 else ant_http1_buffer_appendf(&buf, "Content-Length: %zu\r\n", body_size);
235 ant_http1_buffer_append_cstr(&buf, keep_alive ? "Connection: keep-alive\r\n\r\n" : "Connection: close\r\n\r\n");
236
237 if (buf.failed) {
238 ant_http1_buffer_free(&buf);
239 return js_mkerr_typed(js, JS_ERR_TYPE, "Out of memory");
240 }
241
242 return http_writer_make_buffer_value(js, &buf);
243}
244
245ant_value_t internal_http_writer_library(ant_t *js) {
246 ant_value_t lib = js_mkobj(js);
247
248 js_set(js, lib, "defaultStatusText", js_mkfun(js_http_writer_default_status_text));
249 js_set(js, lib, "writeHead", js_mkfun(js_http_writer_write_head));
250 js_set(js, lib, "writeBasicResponse", js_mkfun(js_http_writer_write_basic_response));
251 js_set(js, lib, "writeChunk", js_mkfun(js_http_writer_write_chunk));
252 js_set(js, lib, "writeFinalChunk", js_mkfun(js_http_writer_write_final_chunk));
253
254 return lib;
255}