Line data Source code
1 : #include "../../util/fd_util.h"
2 : #include "../../ballet/base64/fd_base64.h"
3 : #include <stdlib.h>
4 : #include <string.h>
5 : #include <stdio.h>
6 : #include <errno.h>
7 : #include <time.h>
8 : #include <signal.h>
9 : #include <unistd.h>
10 : #include <stdarg.h>
11 : #include <strings.h>
12 : #include <sys/types.h>
13 : #include <sys/socket.h>
14 : #include "fd_methods.h"
15 : #include "fd_webserver.h"
16 : #include "../../waltz/http/fd_http_server_private.h"
17 :
18 : struct fd_websocket_ctx {
19 : fd_webserver_t * ws;
20 : ulong connection_id;
21 : };
22 :
23 : static void
24 0 : fd_web_reply_flush( fd_webserver_t * ws ) {
25 0 : if( ws->quick_size ) {
26 0 : fd_http_server_memcpy(ws->server, (const uchar*)ws->quick_buf, ws->quick_size);
27 0 : ws->quick_size = 0;
28 0 : }
29 0 : }
30 :
31 : void
32 0 : fd_web_reply_new( fd_webserver_t * ws ) {
33 0 : ws->quick_size = 0;
34 0 : fd_http_server_stage_trunc( ws->server, 0 );
35 0 : ws->prev_reply_len = 0;
36 0 : ws->status_code = 200; // OK
37 0 : }
38 :
39 : // Parse the top level json request object
40 : static void
41 0 : json_parse_root(fd_webserver_t * ws, json_lex_state_t* lex) {
42 0 : struct json_path path;
43 0 : if (json_lex_next_token(lex) == JSON_TOKEN_LBRACKET) {
44 : /* We have an array of requests */
45 0 : fd_web_reply_append(ws, "[", 1);
46 0 : while(1) {
47 0 : fd_web_reply_flush( ws );
48 0 : ws->prev_reply_len = fd_http_server_stage_len( ws->server );
49 :
50 0 : struct json_values values;
51 0 : json_values_new(&values);
52 0 : path.len = 0;
53 0 : if (json_values_parse(lex, &values, &path)) {
54 0 : fd_webserver_method_generic(&values, ws->cb_arg);
55 0 : } else {
56 0 : ulong sz;
57 0 : const char* text = json_lex_get_text(lex, &sz);
58 0 : char text2[4096];
59 0 : snprintf( text2, sizeof(text2), "Parse error: %s", text );
60 0 : fd_web_reply_error( ws, -1, text2, "null" );
61 0 : json_values_delete(&values);
62 0 : break;
63 0 : }
64 0 : json_values_delete(&values);
65 :
66 0 : long tok = json_lex_next_token(lex);
67 0 : if( tok == JSON_TOKEN_COMMA ) {
68 0 : fd_web_reply_append(ws, ",", 1);
69 0 : } else if( tok == JSON_TOKEN_RBRACKET ) {
70 0 : break;
71 0 : } else {
72 0 : fd_web_reply_append(ws, ",", 1);
73 0 : fd_web_reply_flush( ws );
74 0 : ws->prev_reply_len = fd_http_server_stage_len( ws->server );
75 0 : fd_web_reply_error( ws, -1, "Parse error: missing , or ]", "null" );
76 0 : break;
77 0 : }
78 0 : }
79 0 : fd_web_reply_append(ws, "]", 1);
80 :
81 0 : } else {
82 : /* Go back to the first token */
83 0 : lex->pos = 0;
84 0 : lex->last_tok = JSON_TOKEN_ERROR;
85 :
86 0 : struct json_values values;
87 0 : json_values_new(&values);
88 0 : path.len = 0;
89 0 : if (json_values_parse(lex, &values, &path)) {
90 0 : fd_webserver_method_generic(&values, ws->cb_arg);
91 0 : } else {
92 0 : ulong sz;
93 0 : const char* text = json_lex_get_text(lex, &sz);
94 0 : char text2[4096];
95 0 : snprintf( text2, sizeof(text2), "Parse error: %s", text );
96 0 : fd_web_reply_error( ws, -1, text2, "null" );
97 0 : }
98 0 : json_values_delete(&values);
99 0 : }
100 0 : }
101 :
102 : void
103 0 : fd_web_reply_error( fd_webserver_t * ws, int errcode, const char * text, const char * call_id ) {
104 0 : ws->quick_size = 0;
105 0 : fd_http_server_stage_trunc(ws->server, ws->prev_reply_len);
106 0 : fd_web_reply_sprintf(ws, "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":%d,\"message\":", errcode);
107 0 : fd_web_reply_encode_json_string(ws, text);
108 0 : fd_web_reply_sprintf(ws, "},\"id\":%s}", call_id );
109 0 : }
110 :
111 : static void
112 0 : fd_web_protocol_error( fd_webserver_t * ws, const char* text ) {
113 0 : #define CRLF "\r\n"
114 0 : static const char* DOC1 =
115 0 : "<html>" CRLF
116 0 : "<head>" CRLF
117 0 : "<title>ERROR</title>" CRLF
118 0 : "</head>" CRLF
119 0 : "<body>" CRLF
120 0 : "<p><em>";
121 0 : static const char* DOC2 =
122 0 : "</em></p>" CRLF
123 0 : "</body>" CRLF
124 0 : "</html>" CRLF;
125 :
126 0 : fd_web_reply_new(ws);
127 0 : fd_http_server_memcpy(ws->server, (const uchar*)DOC1, strlen(DOC1));
128 0 : fd_http_server_memcpy(ws->server, (const uchar*)text, strlen(text));
129 0 : fd_http_server_memcpy(ws->server, (const uchar*)DOC2, strlen(DOC2));
130 :
131 0 : ws->status_code = 400; // BAD_REQUEST
132 0 : }
133 :
134 : static fd_http_server_response_t
135 0 : request( fd_http_server_request_t const * request ) {
136 0 : fd_webserver_t * ws = (fd_webserver_t *)request->ctx;
137 0 : fd_web_reply_new( ws );
138 :
139 0 : if( FD_LIKELY( request->method==FD_HTTP_SERVER_METHOD_GET ) ) {
140 0 : if( FD_LIKELY( request->headers.upgrade_websocket ) ) {
141 0 : fd_http_server_response_t response = {
142 0 : .status = 200,
143 0 : .upgrade_websocket = 1,
144 0 : .content_type = "application/json",
145 0 : };
146 0 : return response;
147 0 : }
148 :
149 0 : fd_web_protocol_error( ws, "GET method not supported!" );
150 :
151 0 : fd_http_server_response_t response = {
152 0 : .status = 400,
153 0 : .upgrade_websocket = 0,
154 0 : .content_type = "text/html",
155 0 : };
156 0 : FD_TEST( !fd_http_server_stage_body( ws->server, &response ) );
157 0 : return response;
158 :
159 0 : } else if( request->method==FD_HTTP_SERVER_METHOD_OPTIONS ) {
160 0 : fd_http_server_response_t response = {
161 0 : .status = 204UL,
162 0 : .upgrade_websocket = 0,
163 0 : .content_type = NULL,
164 0 : .access_control_allow_origin = "*",
165 0 : .access_control_allow_methods = "POST, GET, OPTIONS",
166 0 : .access_control_allow_headers = "*",
167 0 : .access_control_max_age = 86400,
168 0 : };
169 0 : return response;
170 :
171 0 : } else {
172 0 : if( strcmp(request->path, "/") != 0 ) {
173 0 : fd_web_protocol_error( ws, "POST path must be \"/\"" );
174 :
175 0 : } else if( strncasecmp(request->headers.content_type, "application/json", 16) != 0 ) {
176 0 : fd_web_protocol_error( ws, "content type must be \"application/json\"" );
177 :
178 0 : } else {
179 : #ifdef FD_RPC_DEBUG
180 : fwrite((const char*)request->post.body, 1, request->post.body_len, stdout);
181 : fwrite("\n", 1, 1, stdout);
182 : fflush(stdout);
183 : #endif
184 0 : FD_SPAD_FRAME_BEGIN( ws->spad ) {
185 0 : json_lex_state_t lex;
186 0 : json_lex_state_new(&lex, (const char*)request->post.body, request->post.body_len, ws->spad);
187 0 : json_parse_root(ws, &lex);
188 0 : json_lex_state_delete(&lex);
189 0 : fd_web_reply_flush( ws );
190 0 : } FD_SPAD_FRAME_END;
191 0 : }
192 :
193 0 : fd_http_server_response_t response = {
194 0 : .status = ws->status_code,
195 0 : .upgrade_websocket = 0,
196 0 : .content_type = ( ws->status_code == 200 ? "application/json" : "text/html" ),
197 0 : .access_control_allow_origin = "*",
198 0 : .access_control_allow_methods = "POST, GET, OPTIONS",
199 0 : .access_control_allow_headers = "*",
200 0 : .access_control_max_age = 86400,
201 0 : };
202 0 : if( FD_UNLIKELY( fd_http_server_stage_body( ws->server, &response ) ) ) {
203 0 : FD_LOG_WARNING(( "fd_http_server_stage_body failed" ));
204 0 : fd_http_server_response_t response = {
205 0 : .status = 500,
206 0 : .upgrade_websocket = 0,
207 0 : .content_type = "text/html",
208 0 : .access_control_allow_origin = "*",
209 0 : .access_control_allow_methods = "POST, GET, OPTIONS",
210 0 : .access_control_allow_headers = "*",
211 0 : .access_control_max_age = 86400,
212 0 : };
213 0 : return response;
214 0 : }
215 : #ifdef FD_RPC_DEBUG
216 : fwrite(ws->server->oring + response._body_off, 1, response._body_len, stdout);
217 : fwrite("\n", 1, 1, stdout);
218 : fflush(stdout);
219 : #endif
220 0 : return response;
221 0 : }
222 0 : }
223 :
224 : static void
225 0 : http_open( ulong connection_id, int sockfd, void * ctx ) {
226 0 : (void)connection_id;
227 0 : (void)ctx;
228 :
229 0 : int newsize = 1<<20;
230 0 : int rc = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &newsize, sizeof(newsize));
231 0 : if( FD_UNLIKELY( -1==rc ) ) FD_LOG_ERR(( "setsockopt failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
232 0 : }
233 :
234 : static void
235 0 : http_close( ulong connection_id, int reason, void * ctx ) {
236 0 : (void)connection_id;
237 0 : (void)reason;
238 0 : (void)ctx;
239 0 : }
240 :
241 : static void
242 0 : ws_open( ulong connection_id, void * ctx ) {
243 0 : (void)connection_id;
244 0 : (void)ctx;
245 0 : }
246 :
247 : static void
248 0 : ws_close( ulong connection_id, int reason, void * ctx ) {
249 0 : (void)reason;
250 0 : fd_webserver_t * ws = (fd_webserver_t *)ctx;
251 0 : fd_webserver_ws_closed( connection_id, ws->cb_arg );
252 0 : }
253 :
254 : static void
255 0 : ws_message( ulong conn_id, uchar const * data, ulong data_len, void * ctx ) {
256 : #ifdef FD_RPC_VERBOSE
257 : fwrite("message:\n\n", 1, 9, stdout);
258 : fwrite(data, 1, data_len, stdout);
259 : fwrite("\n\n", 1, 2, stdout);
260 : fflush(stdout);
261 : #endif
262 :
263 0 : fd_webserver_t * ws = (fd_webserver_t *)ctx;
264 0 : fd_web_reply_new( ws );
265 :
266 0 : json_lex_state_t lex;
267 0 : json_lex_state_new(&lex, (const char*)data, data_len, ws->spad);
268 0 : struct json_values values;
269 0 : json_values_new(&values);
270 0 : struct json_path path;
271 0 : path.len = 0;
272 0 : int ret = json_values_parse(&lex, &values, &path);
273 0 : if (ret) {
274 : // json_values_printout(&values);
275 0 : ret = fd_webserver_ws_subscribe(&values, conn_id, ws->cb_arg);
276 0 : } else {
277 0 : ulong sz;
278 0 : const char* text = json_lex_get_text(&lex, &sz);
279 0 : char text2[4096];
280 0 : snprintf( text2, sizeof(text2), "Parse error: %s", text );
281 0 : fd_web_reply_error( ws, -1, text2, "null" );
282 0 : }
283 0 : json_values_delete(&values);
284 0 : json_lex_state_delete(&lex);
285 0 : fd_web_ws_send( ws, conn_id );
286 0 : }
287 :
288 0 : void fd_web_ws_send( fd_webserver_t * ws, ulong conn_id ) {
289 0 : fd_web_reply_flush( ws );
290 0 : fd_http_server_ws_send( ws->server, conn_id );
291 0 : }
292 :
293 0 : int fd_webserver_start( ushort portno, fd_http_server_params_t params, fd_spad_t * spad, fd_webserver_t * ws, void * cb_arg ) {
294 0 : memset(ws, 0, sizeof(fd_webserver_t));
295 :
296 0 : ws->cb_arg = cb_arg;
297 0 : ws->spad = spad;
298 :
299 0 : fd_http_server_callbacks_t callbacks = {
300 0 : .request = request,
301 0 : .open = http_open,
302 0 : .close = http_close,
303 0 : .ws_open = ws_open,
304 0 : .ws_close = ws_close,
305 0 : .ws_message = ws_message,
306 0 : };
307 :
308 0 : void* server_mem = fd_spad_alloc( spad, fd_http_server_align(), fd_http_server_footprint( params ) );
309 0 : ws->server = fd_http_server_join( fd_http_server_new( server_mem, params, callbacks, ws ) );
310 :
311 0 : FD_TEST( fd_http_server_listen( ws->server, 0, portno ) != NULL );
312 :
313 0 : return 0;
314 0 : }
315 :
316 : int
317 0 : fd_webserver_poll(fd_webserver_t * ws) {
318 0 : return fd_http_server_poll( ws->server, 0 );
319 0 : }
320 :
321 : int
322 0 : fd_webserver_fd(fd_webserver_t * ws) {
323 0 : return fd_http_server_fd( ws->server );
324 0 : }
325 :
326 : int
327 : fd_web_reply_append( fd_webserver_t * ws,
328 : const char * text,
329 0 : ulong text_sz ) {
330 0 : if( FD_LIKELY( ws->quick_size + text_sz <= FD_WEBSERVER_QUICK_MAX ) ) {
331 0 : memcpy( ws->quick_buf + ws->quick_size, text, text_sz );
332 0 : ws->quick_size += text_sz;
333 0 : } else {
334 0 : fd_web_reply_flush( ws );
335 0 : if( FD_LIKELY( text_sz <= FD_WEBSERVER_QUICK_MAX ) ) {
336 0 : memcpy( ws->quick_buf, text, text_sz );
337 0 : ws->quick_size = text_sz;
338 0 : } else {
339 0 : fd_http_server_memcpy( ws->server, (const uchar*)text, text_sz );
340 0 : }
341 0 : }
342 0 : return 0;
343 0 : }
344 :
345 : static const char b58digits_ordered[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
346 :
347 : int
348 : fd_web_reply_encode_base58( fd_webserver_t * ws,
349 : const void * data,
350 0 : ulong data_sz ) {
351 : /* Prevent explosive growth in computation */
352 0 : if (data_sz > 400U)
353 0 : return -1;
354 :
355 0 : const uchar* bin = (const uchar*)data;
356 0 : ulong carry;
357 0 : ulong i, j, high, zcount = 0;
358 0 : ulong size;
359 :
360 0 : while (zcount < data_sz && !bin[zcount])
361 0 : ++zcount;
362 :
363 : /* Temporary buffer size */
364 0 : size = (data_sz - zcount) * 138 / 100 + 1;
365 0 : uchar buf[size];
366 0 : memset(buf, 0, size);
367 :
368 0 : for (i = zcount, high = size - 1; i < data_sz; ++i, high = j) {
369 0 : for (carry = bin[i], j = size - 1; (j > high) || carry; --j) {
370 0 : carry += 256UL * (ulong)buf[j];
371 0 : buf[j] = (uchar)(carry % 58);
372 0 : carry /= 58UL;
373 0 : if (!j) {
374 : // Otherwise j wraps to maxint which is > high
375 0 : break;
376 0 : }
377 0 : }
378 0 : }
379 :
380 0 : for (j = 0; j < size && !buf[j]; ++j) ;
381 :
382 0 : ulong out_sz = zcount + size - j;
383 0 : char b58[out_sz];
384 0 : if (zcount)
385 0 : fd_memset(b58, '1', zcount);
386 0 : for (i = zcount; j < size; ++i, ++j)
387 0 : b58[i] = b58digits_ordered[buf[j]];
388 :
389 0 : return fd_web_reply_append( ws, b58, out_sz );
390 0 : }
391 :
392 : static char base64_encoding_table[] = {
393 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
394 : 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
395 : 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
396 : 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
397 : 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
398 : 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
399 : 'w', 'x', 'y', 'z', '0', '1', '2', '3',
400 : '4', '5', '6', '7', '8', '9', '+', '/'
401 : };
402 :
403 : int
404 : fd_web_reply_encode_base64( fd_webserver_t * ws,
405 : const void * data,
406 0 : ulong data_sz ) {
407 0 : for (ulong i = 0; i < data_sz; ) {
408 0 : if( FD_UNLIKELY( ws->quick_size + 4U > FD_WEBSERVER_QUICK_MAX ) ) {
409 0 : fd_web_reply_flush( ws );
410 0 : }
411 0 : char * out_data = ws->quick_buf + ws->quick_size;
412 0 : switch (data_sz - i) {
413 0 : default: { /* 3 and above */
414 0 : uint octet_a = ((uchar*)data)[i++];
415 0 : uint octet_b = ((uchar*)data)[i++];
416 0 : uint octet_c = ((uchar*)data)[i++];
417 0 : uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
418 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
419 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
420 0 : out_data[2] = base64_encoding_table[(triple >> 1 * 6) & 0x3F];
421 0 : out_data[3] = base64_encoding_table[(triple >> 0 * 6) & 0x3F];
422 0 : break;
423 0 : }
424 0 : case 2: {
425 0 : uint octet_a = ((uchar*)data)[i++];
426 0 : uint octet_b = ((uchar*)data)[i++];
427 0 : uint triple = (octet_a << 0x10) + (octet_b << 0x08);
428 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
429 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
430 0 : out_data[2] = base64_encoding_table[(triple >> 1 * 6) & 0x3F];
431 0 : out_data[3] = '=';
432 0 : break;
433 0 : }
434 0 : case 1: {
435 0 : uint octet_a = ((uchar*)data)[i++];
436 0 : uint triple = (octet_a << 0x10);
437 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
438 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
439 0 : out_data[2] = '=';
440 0 : out_data[3] = '=';
441 0 : break;
442 0 : }
443 0 : }
444 0 : ws->quick_size += 4U;
445 0 : }
446 0 : return 0;
447 0 : }
448 :
449 : static const char hex_encoding_table[] = "0123456789ABCDEF";
450 :
451 : int
452 : fd_web_reply_encode_hex( fd_webserver_t * ws,
453 : const void * data,
454 0 : ulong data_sz ) {
455 0 : for (ulong i = 0; i < data_sz; ) {
456 0 : if( FD_UNLIKELY( ws->quick_size + 2U > FD_WEBSERVER_QUICK_MAX ) ) {
457 0 : fd_web_reply_flush( ws );
458 0 : }
459 0 : char * out_data = ws->quick_buf + ws->quick_size;
460 0 : uint octet = ((uchar*)data)[i++];
461 0 : out_data[0] = hex_encoding_table[(octet >> 4) & 0xF];
462 0 : out_data[1] = hex_encoding_table[octet & 0xF];
463 0 : ws->quick_size += 2U;
464 0 : }
465 0 : return 0;
466 0 : }
467 :
468 : int
469 0 : fd_web_reply_sprintf( fd_webserver_t * ws, const char* format, ... ) {
470 0 : ulong remain = FD_WEBSERVER_QUICK_MAX - ws->quick_size;
471 0 : char * buf = ws->quick_buf + ws->quick_size;
472 0 : va_list ap;
473 0 : va_start(ap, format);
474 0 : int r = vsnprintf(buf, remain, format, ap);
475 0 : va_end(ap);
476 0 : if( FD_UNLIKELY( r < 0 ) ) return -1;
477 0 : if( FD_LIKELY( (uint)r < remain ) ) {
478 0 : ws->quick_size += (uint)r;
479 0 : return 0;
480 0 : }
481 :
482 0 : fd_web_reply_flush( ws );
483 0 : buf = ws->quick_buf;
484 0 : va_start(ap, format);
485 0 : r = vsnprintf(buf, FD_WEBSERVER_QUICK_MAX, format, ap);
486 0 : va_end(ap);
487 0 : if( r < 0 || (uint)r >= FD_WEBSERVER_QUICK_MAX ) return -1;
488 0 : ws->quick_size = (uint)r;
489 0 : return 0;
490 0 : }
491 :
492 : int
493 0 : fd_web_reply_encode_json_string( fd_webserver_t * ws, const char * str ) {
494 0 : char buf[512];
495 0 : buf[0] = '"';
496 0 : ulong buflen = 1;
497 0 : while( *str ) {
498 :
499 : /* UTF-8 decode */
500 0 : uint c = (uchar)(*str);
501 0 : uint k = (uint)__builtin_clz(~(c << 24U)); // Count # of leading 1 bits.
502 : /* k = 0 for one-byte code points; otherwise, k = #total bytes. */
503 0 : uint value = c;
504 0 : if( k ) {
505 0 : value &= (1U << (8U - k)) - 1U; // All 1s with k+1 leading 0s.
506 0 : for ((c = (uchar)(*(++str))), --k; k > 0; --k, (c = (uchar)(*(++str)))) {
507 : /* tests if a char is a continuation byte in utf8. */
508 0 : if( (c & 0xc0U) != 0x80U ) return -1;
509 0 : value <<= 6;
510 0 : value += (c & 0x3FU);
511 0 : }
512 0 : }
513 :
514 0 : switch( value ) {
515 0 : case (uchar)'\\': buf[buflen++] = '\\'; buf[buflen++] = '\\'; break;
516 0 : case (uchar)'\"': buf[buflen++] = '\\'; buf[buflen++] = '\"'; break;
517 0 : case (uchar)'\n': buf[buflen++] = '\\'; buf[buflen++] = 'n'; break;
518 0 : case (uchar)'\t': buf[buflen++] = '\\'; buf[buflen++] = 't'; break;
519 0 : case (uchar)'\r': buf[buflen++] = '\\'; buf[buflen++] = 'r'; break;
520 0 : default:
521 0 : if( value >= 0x20 && value <= 0x7F ) {
522 0 : buf[buflen++] = (char)value;
523 0 : } else {
524 0 : buflen += (uint)snprintf(buf + buflen, sizeof(buf) - buflen - 1U, "\\u%04x", value);
525 0 : }
526 0 : }
527 :
528 0 : if( buflen >= sizeof(buf)-10U ) {
529 0 : int err = fd_web_reply_append( ws, buf, buflen );
530 0 : if( err ) return err;
531 0 : buflen = 0;
532 0 : }
533 :
534 0 : ++str;
535 0 : }
536 0 : buf[buflen++] = '"';
537 0 : return fd_web_reply_append( ws, buf, buflen );
538 0 : }
|