LCOV - code coverage report
Current view: top level - flamenco/rpcserver - fd_webserver.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 391 0.0 %
Date: 2024-11-13 11:58:15 Functions: 0 22 0.0 %

          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 : }

Generated by: LCOV version 1.14