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 "../../ballet/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_SCRATCH_SCOPE_BEGIN {
185 0 : json_lex_state_t lex;
186 0 : json_lex_state_new(&lex, (const char*)request->post.body, request->post.body_len);
187 0 : json_parse_root(ws, &lex);
188 0 : json_lex_state_delete(&lex);
189 0 : fd_web_reply_flush( ws );
190 0 : } FD_SCRATCH_SCOPE_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 : };
199 0 : if( FD_UNLIKELY( fd_http_server_stage_body( ws->server, &response ) ) ) {
200 0 : FD_LOG_WARNING(( "fd_http_server_stage_body failed" ));
201 0 : fd_http_server_response_t response = {
202 0 : .status = 500,
203 0 : .upgrade_websocket = 0,
204 0 : .content_type = "text/html",
205 0 : .access_control_allow_origin = "*",
206 0 : };
207 0 : return response;
208 0 : }
209 : #ifdef FD_RPC_DEBUG
210 : fwrite(ws->server->oring + response._body_off, 1, response._body_len, stdout);
211 : fwrite("\n", 1, 1, stdout);
212 : fflush(stdout);
213 : #endif
214 0 : return response;
215 0 : }
216 0 : }
217 :
218 : static void
219 0 : http_open( ulong connection_id, int sockfd, void * ctx ) {
220 0 : (void)connection_id;
221 0 : (void)ctx;
222 :
223 0 : int newsize = 1<<20;
224 0 : int rc = setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &newsize, sizeof(newsize));
225 0 : if( FD_UNLIKELY( -1==rc ) ) FD_LOG_ERR(( "setsockopt failed (%i-%s)", errno, strerror( errno ) )); /* Unexpected programmer error, abort */
226 0 : }
227 :
228 : static void
229 0 : http_close( ulong connection_id, int reason, void * ctx ) {
230 0 : (void)connection_id;
231 0 : (void)reason;
232 0 : (void)ctx;
233 0 : }
234 :
235 : static void
236 0 : ws_open( ulong connection_id, void * ctx ) {
237 0 : (void)connection_id;
238 0 : (void)ctx;
239 0 : }
240 :
241 : static void
242 0 : ws_close( ulong connection_id, int reason, void * ctx ) {
243 0 : (void)reason;
244 0 : fd_webserver_t * ws = (fd_webserver_t *)ctx;
245 0 : fd_webserver_ws_closed( connection_id, ws->cb_arg );
246 0 : }
247 :
248 : static void
249 0 : ws_message( ulong conn_id, uchar const * data, ulong data_len, void * ctx ) {
250 : #ifdef FD_RPC_VERBOSE
251 : fwrite("message:\n\n", 1, 9, stdout);
252 : fwrite(data, 1, data_len, stdout);
253 : fwrite("\n\n", 1, 2, stdout);
254 : fflush(stdout);
255 : #endif
256 :
257 0 : fd_webserver_t * ws = (fd_webserver_t *)ctx;
258 0 : fd_web_reply_new( ws );
259 :
260 0 : json_lex_state_t lex;
261 0 : json_lex_state_new(&lex, (const char*)data, data_len);
262 0 : struct json_values values;
263 0 : json_values_new(&values);
264 0 : struct json_path path;
265 0 : path.len = 0;
266 0 : int ret = json_values_parse(&lex, &values, &path);
267 0 : if (ret) {
268 : // json_values_printout(&values);
269 0 : ret = fd_webserver_ws_subscribe(&values, conn_id, ws->cb_arg);
270 0 : } else {
271 0 : ulong sz;
272 0 : const char* text = json_lex_get_text(&lex, &sz);
273 0 : char text2[4096];
274 0 : snprintf( text2, sizeof(text2), "Parse error: %s", text );
275 0 : fd_web_reply_error( ws, -1, text2, "null" );
276 0 : }
277 0 : json_values_delete(&values);
278 0 : json_lex_state_delete(&lex);
279 0 : fd_web_ws_send( ws, conn_id );
280 0 : }
281 :
282 0 : void fd_web_ws_send( fd_webserver_t * ws, ulong conn_id ) {
283 0 : fd_web_reply_flush( ws );
284 0 : fd_http_server_ws_send( ws->server, conn_id );
285 0 : }
286 :
287 0 : int fd_webserver_start( ushort portno, fd_http_server_params_t params, fd_valloc_t valloc, fd_webserver_t * ws, void * cb_arg ) {
288 0 : memset(ws, 0, sizeof(fd_webserver_t));
289 :
290 0 : ws->cb_arg = cb_arg;
291 :
292 0 : fd_http_server_callbacks_t callbacks = {
293 0 : .request = request,
294 0 : .open = http_open,
295 0 : .close = http_close,
296 0 : .ws_open = ws_open,
297 0 : .ws_close = ws_close,
298 0 : .ws_message = ws_message,
299 0 : };
300 :
301 0 : void* server_mem = fd_valloc_malloc( valloc, fd_http_server_align(), fd_http_server_footprint( params ) );
302 0 : ws->server = fd_http_server_join( fd_http_server_new( server_mem, params, callbacks, ws ) );
303 :
304 0 : FD_TEST( fd_http_server_listen( ws->server, 0, portno ) != NULL );
305 :
306 0 : return 0;
307 0 : }
308 :
309 0 : int fd_webserver_stop(fd_valloc_t valloc, fd_webserver_t * ws) {
310 0 : fd_valloc_free( valloc, fd_http_server_delete( fd_http_server_leave( ws->server ) ) );
311 0 : return 0;
312 0 : }
313 :
314 : int
315 0 : fd_webserver_poll(fd_webserver_t * ws) {
316 0 : return fd_http_server_poll( ws->server );
317 0 : }
318 :
319 : int
320 0 : fd_webserver_fd(fd_webserver_t * ws) {
321 0 : return fd_http_server_fd( ws->server );
322 0 : }
323 :
324 : int
325 : fd_web_reply_append( fd_webserver_t * ws,
326 : const char * text,
327 0 : ulong text_sz ) {
328 0 : if( FD_LIKELY( ws->quick_size + text_sz <= FD_WEBSERVER_QUICK_MAX ) ) {
329 0 : memcpy( ws->quick_buf + ws->quick_size, text, text_sz );
330 0 : ws->quick_size += text_sz;
331 0 : } else {
332 0 : fd_web_reply_flush( ws );
333 0 : if( FD_LIKELY( text_sz <= FD_WEBSERVER_QUICK_MAX ) ) {
334 0 : memcpy( ws->quick_buf, text, text_sz );
335 0 : ws->quick_size = text_sz;
336 0 : } else {
337 0 : fd_http_server_memcpy( ws->server, (const uchar*)text, text_sz );
338 0 : }
339 0 : }
340 0 : return 0;
341 0 : }
342 :
343 : static const char b58digits_ordered[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
344 :
345 : int
346 : fd_web_reply_encode_base58( fd_webserver_t * ws,
347 : const void * data,
348 0 : ulong data_sz ) {
349 : /* Prevent explosive growth in computation */
350 0 : if (data_sz > 400U)
351 0 : return -1;
352 :
353 0 : const uchar* bin = (const uchar*)data;
354 0 : ulong carry;
355 0 : ulong i, j, high, zcount = 0;
356 0 : ulong size;
357 :
358 0 : while (zcount < data_sz && !bin[zcount])
359 0 : ++zcount;
360 :
361 : /* Temporary buffer size */
362 0 : size = (data_sz - zcount) * 138 / 100 + 1;
363 0 : uchar buf[size];
364 0 : memset(buf, 0, size);
365 :
366 0 : for (i = zcount, high = size - 1; i < data_sz; ++i, high = j) {
367 0 : for (carry = bin[i], j = size - 1; (j > high) || carry; --j) {
368 0 : carry += 256UL * (ulong)buf[j];
369 0 : buf[j] = (uchar)(carry % 58);
370 0 : carry /= 58UL;
371 0 : if (!j) {
372 : // Otherwise j wraps to maxint which is > high
373 0 : break;
374 0 : }
375 0 : }
376 0 : }
377 :
378 0 : for (j = 0; j < size && !buf[j]; ++j) ;
379 :
380 0 : ulong out_sz = zcount + size - j;
381 0 : char b58[out_sz];
382 0 : if (zcount)
383 0 : fd_memset(b58, '1', zcount);
384 0 : for (i = zcount; j < size; ++i, ++j)
385 0 : b58[i] = b58digits_ordered[buf[j]];
386 :
387 0 : return fd_web_reply_append( ws, b58, out_sz );
388 0 : }
389 :
390 : static char base64_encoding_table[] = {
391 : 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
392 : 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
393 : 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
394 : 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
395 : 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
396 : 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
397 : 'w', 'x', 'y', 'z', '0', '1', '2', '3',
398 : '4', '5', '6', '7', '8', '9', '+', '/'
399 : };
400 :
401 : int
402 : fd_web_reply_encode_base64( fd_webserver_t * ws,
403 : const void * data,
404 0 : ulong data_sz ) {
405 0 : for (ulong i = 0; i < data_sz; ) {
406 0 : if( FD_UNLIKELY( ws->quick_size + 4U > FD_WEBSERVER_QUICK_MAX ) ) {
407 0 : fd_web_reply_flush( ws );
408 0 : }
409 0 : char * out_data = ws->quick_buf + ws->quick_size;
410 0 : switch (data_sz - i) {
411 0 : default: { /* 3 and above */
412 0 : uint octet_a = ((uchar*)data)[i++];
413 0 : uint octet_b = ((uchar*)data)[i++];
414 0 : uint octet_c = ((uchar*)data)[i++];
415 0 : uint triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c;
416 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
417 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
418 0 : out_data[2] = base64_encoding_table[(triple >> 1 * 6) & 0x3F];
419 0 : out_data[3] = base64_encoding_table[(triple >> 0 * 6) & 0x3F];
420 0 : break;
421 0 : }
422 0 : case 2: {
423 0 : uint octet_a = ((uchar*)data)[i++];
424 0 : uint octet_b = ((uchar*)data)[i++];
425 0 : uint triple = (octet_a << 0x10) + (octet_b << 0x08);
426 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
427 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
428 0 : out_data[2] = base64_encoding_table[(triple >> 1 * 6) & 0x3F];
429 0 : out_data[3] = '=';
430 0 : break;
431 0 : }
432 0 : case 1: {
433 0 : uint octet_a = ((uchar*)data)[i++];
434 0 : uint triple = (octet_a << 0x10);
435 0 : out_data[0] = base64_encoding_table[(triple >> 3 * 6) & 0x3F];
436 0 : out_data[1] = base64_encoding_table[(triple >> 2 * 6) & 0x3F];
437 0 : out_data[2] = '=';
438 0 : out_data[3] = '=';
439 0 : break;
440 0 : }
441 0 : }
442 0 : ws->quick_size += 4U;
443 0 : }
444 0 : return 0;
445 0 : }
446 :
447 : static const char hex_encoding_table[] = "0123456789ABCDEF";
448 :
449 : int
450 : fd_web_reply_encode_hex( fd_webserver_t * ws,
451 : const void * data,
452 0 : ulong data_sz ) {
453 0 : for (ulong i = 0; i < data_sz; ) {
454 0 : if( FD_UNLIKELY( ws->quick_size + 2U > FD_WEBSERVER_QUICK_MAX ) ) {
455 0 : fd_web_reply_flush( ws );
456 0 : }
457 0 : char * out_data = ws->quick_buf + ws->quick_size;
458 0 : uint octet = ((uchar*)data)[i++];
459 0 : out_data[0] = hex_encoding_table[(octet >> 4) & 0xF];
460 0 : out_data[1] = hex_encoding_table[octet & 0xF];
461 0 : ws->quick_size += 2U;
462 0 : }
463 0 : return 0;
464 0 : }
465 :
466 : int
467 0 : fd_web_reply_sprintf( fd_webserver_t * ws, const char* format, ... ) {
468 0 : ulong remain = FD_WEBSERVER_QUICK_MAX - ws->quick_size;
469 0 : char * buf = ws->quick_buf + ws->quick_size;
470 0 : va_list ap;
471 0 : va_start(ap, format);
472 0 : int r = vsnprintf(buf, remain, format, ap);
473 0 : va_end(ap);
474 0 : if( FD_UNLIKELY( r < 0 ) ) return -1;
475 0 : if( FD_LIKELY( (uint)r < remain ) ) {
476 0 : ws->quick_size += (uint)r;
477 0 : return 0;
478 0 : }
479 :
480 0 : fd_web_reply_flush( ws );
481 0 : buf = ws->quick_buf;
482 0 : va_start(ap, format);
483 0 : r = vsnprintf(buf, FD_WEBSERVER_QUICK_MAX, format, ap);
484 0 : va_end(ap);
485 0 : if( r < 0 || (uint)r >= FD_WEBSERVER_QUICK_MAX ) return -1;
486 0 : ws->quick_size = (uint)r;
487 0 : return 0;
488 0 : }
489 :
490 : int
491 0 : fd_web_reply_encode_json_string( fd_webserver_t * ws, const char * str ) {
492 0 : char buf[512];
493 0 : buf[0] = '"';
494 0 : ulong buflen = 1;
495 0 : while( *str ) {
496 :
497 : /* UTF-8 decode */
498 0 : uint c = (uchar)(*str);
499 0 : uint k = (uint)__builtin_clz(~(c << 24U)); // Count # of leading 1 bits.
500 : /* k = 0 for one-byte code points; otherwise, k = #total bytes. */
501 0 : uint value = c;
502 0 : if( k ) {
503 0 : value &= (1U << (8U - k)) - 1U; // All 1s with k+1 leading 0s.
504 0 : for ((c = (uchar)(*(++str))), --k; k > 0; --k, (c = (uchar)(*(++str)))) {
505 : /* tests if a char is a continuation byte in utf8. */
506 0 : if( (c & 0xc0U) != 0x80U ) return -1;
507 0 : value <<= 6;
508 0 : value += (c & 0x3FU);
509 0 : }
510 0 : }
511 :
512 0 : switch( value ) {
513 0 : case (uchar)'\\': buf[buflen++] = '\\'; buf[buflen++] = '\\'; break;
514 0 : case (uchar)'\"': buf[buflen++] = '\\'; buf[buflen++] = '\"'; break;
515 0 : case (uchar)'\n': buf[buflen++] = '\\'; buf[buflen++] = 'n'; break;
516 0 : case (uchar)'\t': buf[buflen++] = '\\'; buf[buflen++] = 't'; break;
517 0 : case (uchar)'\r': buf[buflen++] = '\\'; buf[buflen++] = 'r'; break;
518 0 : default:
519 0 : if( value >= 0x20 && value <= 0x7F ) {
520 0 : buf[buflen++] = (char)value;
521 0 : } else {
522 0 : buflen += (uint)snprintf(buf + buflen, sizeof(buf) - buflen - 1U, "\\u%04x", value);
523 0 : }
524 0 : }
525 :
526 0 : if( buflen >= sizeof(buf)-10U ) {
527 0 : int err = fd_web_reply_append( ws, buf, buflen );
528 0 : if( err ) return err;
529 0 : buflen = 0;
530 0 : }
531 :
532 0 : ++str;
533 0 : }
534 0 : buf[buflen++] = '"';
535 0 : return fd_web_reply_append( ws, buf, buflen );
536 0 : }
|