LCOV - code coverage report
Current view: top level - lib - lwan-response.c (source / functions) Hit Total Coverage
Test: coverage.info.cleaned Lines: 141 204 69.1 %
Date: 2023-04-18 16:19:03 Functions: 10 14 71.4 %

          Line data    Source code
       1             : /*
       2             :  * lwan - web server
       3             :  * Copyright (c) 2012 L. A. F. Pereira <l@tia.mat.br>
       4             :  *
       5             :  * This program is free software; you can redistribute it and/or
       6             :  * modify it under the terms of the GNU General Public License
       7             :  * as published by the Free Software Foundation; either version 2
       8             :  * of the License, or any later version.
       9             :  *
      10             :  * This program is distributed in the hope that it will be useful,
      11             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13             :  * GNU General Public License for more details.
      14             :  *
      15             :  * You should have received a copy of the GNU General Public License
      16             :  * along with this program; if not, write to the Free Software
      17             :  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
      18             :  * USA.
      19             :  */
      20             : 
      21             : #define _GNU_SOURCE
      22             : #include <assert.h>
      23             : #include <netinet/in.h>
      24             : #include <string.h>
      25             : #include <sys/socket.h>
      26             : #include <sys/uio.h>
      27             : #include <unistd.h>
      28             : 
      29             : #include "lwan-private.h"
      30             : 
      31             : #include "int-to-str.h"
      32             : #include "lwan-io-wrappers.h"
      33             : #include "lwan-template.h"
      34             : 
      35             : static struct lwan_tpl *error_template = NULL;
      36             : 
      37             : static const char *error_template_str = "<html><head><style>" \
      38             :     "body{" \
      39             :     "background:#627d4d;" \
      40             :     "background:-moz-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
      41             :     "background:-webkit-gradient(radial,center center,0px,center center,100\x25,color-stop(15\x25,#627d4d),color-stop(100\x25,#1f3b08));" \
      42             :     "background:-webkit-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
      43             :     "background:-o-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
      44             :     "background:-ms-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
      45             :     "background:radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
      46             :     "height:100\x25;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;text-align:center;border:0;letter-spacing:-1px;margin:0;padding:0}.sorry{color:#244837;font-size:18px;line-height:24px;text-shadow:0" \
      47             :     "1px 1px rgba(255,255,255,0.33)}h1{color:#fff;font-size:30px;font-weight:700;text-shadow:0 1px 4px rgba(0,0,0,0.68);letter-spacing:-1px;margin:0}" \
      48             :     "</style>" \
      49             :     "</head>" \
      50             :     "<body>" \
      51             :     "<table height=\"100\x25\" width=\"100\x25\"><tr><td align=\"center\" valign=\"middle\">" \
      52             :     "<div>" \
      53             :     "<h1>{{short_message}}</h1>" \
      54             :     "<div class=\"sorry\">" \
      55             :     "<p>{{long_message}}</p>" \
      56             :     "</div>" \
      57             :     "</div>" \
      58             :     "</td></tr></table>" \
      59             :     "</body>" \
      60             :     "</html>";
      61             : 
      62             : struct error_template {
      63             :     const char *short_message;
      64             :     const char *long_message;
      65             : };
      66             : 
      67          92 : void lwan_response_init(struct lwan *l)
      68             : {
      69             : #undef TPL_STRUCT
      70             : #define TPL_STRUCT struct error_template
      71             :     static const struct lwan_var_descriptor error_descriptor[] = {
      72             :         TPL_VAR_STR(short_message), TPL_VAR_STR(long_message),
      73             :         TPL_VAR_SENTINEL};
      74             : 
      75          92 :     assert(!error_template);
      76             : 
      77          92 :     lwan_status_debug("Initializing default response");
      78             : 
      79          92 :     if (l->config.error_template) {
      80           0 :         error_template =
      81           0 :             lwan_tpl_compile_file(l->config.error_template, error_descriptor);
      82             :     } else {
      83          92 :         error_template = lwan_tpl_compile_string_full(
      84             :             error_template_str, error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE);
      85             :     }
      86             : 
      87          92 :     if (UNLIKELY(!error_template))
      88           0 :         lwan_status_critical_perror("lwan_tpl_compile_string");
      89          92 : }
      90             : 
      91           0 : void lwan_response_shutdown(struct lwan *l __attribute__((unused)))
      92             : {
      93           0 :     lwan_status_debug("Shutting down response");
      94           0 :     assert(error_template);
      95           0 :     lwan_tpl_free(error_template);
      96           0 : }
      97             : 
      98         499 : static inline bool has_response_body(enum lwan_request_flags method,
      99             :                                      enum lwan_http_status status)
     100             : {
     101             :     /* See FOR_EACH_REQUEST_METHOD() in lwan.h */
     102         499 :     return (method & 1 << 0) || status != HTTP_NOT_MODIFIED;
     103             : }
     104             : 
     105         544 : void lwan_response(struct lwan_request *request, enum lwan_http_status status)
     106             : {
     107         544 :     const struct lwan_response *response = &request->response;
     108             :     char headers[DEFAULT_HEADERS_SIZE];
     109             : 
     110         544 :     if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) {
     111             :         /* Send last, 0-sized chunk */
     112           2 :         lwan_strbuf_reset(response->buffer);
     113           2 :         lwan_response_send_chunk(request);
     114           2 :         return;
     115             :     }
     116             : 
     117         542 :     if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) {
     118           0 :         lwan_status_debug("Headers already sent, ignoring call");
     119           0 :         return;
     120             :     }
     121             : 
     122         542 :     if (UNLIKELY(!response->mime_type)) {
     123             :         /* Requests without a MIME Type are errors from handlers that should
     124             :            just be handled by lwan_default_response().  */
     125          33 :         return lwan_default_response(request, status);
     126             :     }
     127             : 
     128         509 :     if (request->flags & RESPONSE_STREAM) {
     129          10 :         if (LIKELY(response->stream.callback)) {
     130          10 :             status = response->stream.callback(request, response->stream.data);
     131             :         } else {
     132           0 :             status = HTTP_INTERNAL_ERROR;
     133             :         }
     134             : 
     135           9 :         if (UNLIKELY(status >= HTTP_CLASS__CLIENT_ERROR)) {
     136           3 :             request->flags &= ~RESPONSE_STREAM;
     137           3 :             lwan_default_response(request, status);
     138             :         }
     139             : 
     140           9 :         return;
     141             :     }
     142             : 
     143             :     size_t header_len =
     144         499 :         lwan_prepare_response_header(request, status, headers, sizeof(headers));
     145         499 :     if (UNLIKELY(!header_len))
     146           0 :         return lwan_default_response(request, HTTP_INTERNAL_ERROR);
     147             : 
     148         499 :     if (!has_response_body(lwan_request_get_method(request), status))
     149           0 :         return (void)lwan_send(request, headers, header_len, 0);
     150             : 
     151         499 :     char *resp_buf = lwan_strbuf_get_buffer(response->buffer);
     152         499 :     const size_t resp_len = lwan_strbuf_get_length(response->buffer);
     153         499 :     if (sizeof(headers) - header_len > resp_len) {
     154             :         /* writev() has to allocate, copy, and validate the response vector,
     155             :          * so use send() for responses small enough to fit the headers
     156             :          * buffer.  On Linux, this is ~10% faster.  */
     157         495 :         memcpy(headers + header_len, resp_buf, resp_len);
     158         495 :         return (void)lwan_send(request, headers, header_len + resp_len, 0);
     159             :     }
     160             : 
     161           4 :     struct iovec response_vec[] = {
     162             :         {.iov_base = headers, .iov_len = header_len},
     163             :         {.iov_base = resp_buf, .iov_len = resp_len},
     164             :     };
     165             : 
     166           4 :     return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec));
     167             : }
     168             : 
     169          41 : void lwan_fill_default_response(struct lwan_strbuf *buffer,
     170             :                                 enum lwan_http_status status)
     171             : {
     172          41 :     lwan_tpl_apply_with_buffer(
     173             :         error_template, buffer,
     174          41 :         &(struct error_template){
     175          41 :             .short_message = lwan_http_status_as_string(status),
     176          41 :             .long_message = lwan_http_status_as_descriptive_string(status),
     177             :         });
     178          41 : }
     179             : 
     180          41 : void lwan_default_response(struct lwan_request *request,
     181             :                            enum lwan_http_status status)
     182             : {
     183          41 :     request->response.mime_type = "text/html";
     184             : 
     185          41 :     lwan_fill_default_response(request->response.buffer, status);
     186          41 :     lwan_response(request, status);
     187          41 : }
     188             : 
     189             : #define RETURN_0_ON_OVERFLOW(len_)                                             \
     190             :     if (UNLIKELY(p_headers + (len_) >= p_headers_end))                         \
     191             :     return 0
     192             : 
     193             : #define APPEND_STRING_LEN(const_str_, len_)                                    \
     194             :     do {                                                                       \
     195             :         RETURN_0_ON_OVERFLOW(len_);                                            \
     196             :         p_headers = mempcpy(p_headers, (const_str_), (len_));                  \
     197             :     } while (0)
     198             : 
     199             : #define APPEND_STRING(str_)                                                    \
     200             :     do {                                                                       \
     201             :         size_t len = strlen(str_);                                             \
     202             :         APPEND_STRING_LEN((str_), len);                                        \
     203             :     } while (0)
     204             : 
     205             : #define APPEND_CHAR(value_)                                                    \
     206             :     do {                                                                       \
     207             :         RETURN_0_ON_OVERFLOW(1);                                               \
     208             :         *p_headers++ = (value_);                                               \
     209             :     } while (0)
     210             : 
     211             : #define APPEND_CHAR_NOCHECK(value_) *p_headers++ = (value_)
     212             : 
     213             : #define APPEND_UINT(value_)                                                    \
     214             :     do {                                                                       \
     215             :         size_t len;                                                            \
     216             :         char *tmp = uint_to_string((value_), buffer, &len);                    \
     217             :         RETURN_0_ON_OVERFLOW(len);                                             \
     218             :         APPEND_STRING_LEN(tmp, len);                                           \
     219             :     } while (0)
     220             : 
     221             : #define APPEND_CONSTANT(const_str_)                                            \
     222             :     APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1)
     223             : 
     224             : static ALWAYS_INLINE __attribute__((const)) bool
     225             : flags_has_content_length(enum lwan_request_flags v)
     226             : {
     227         508 :     return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM |
     228             :                   RESPONSE_CHUNKED_ENCODING));
     229             : }
     230             : 
     231             : static ALWAYS_INLINE __attribute__((const)) bool
     232             : has_uncommon_response_headers(enum lwan_request_flags v)
     233             : {
     234         508 :     return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS |
     235             :                 RESPONSE_CHUNKED_ENCODING | REQUEST_WANTS_HSTS_HEADER);
     236             : }
     237             : 
     238         508 : size_t lwan_prepare_response_header_full(
     239             :     struct lwan_request *request,
     240             :     enum lwan_http_status status,
     241             :     char headers[],
     242             :     size_t headers_buf_size,
     243             :     const struct lwan_key_value *additional_headers)
     244             : {
     245             :     /* NOTE: If new response headers are added here, update
     246             :      * can_override_header() in lwan.c */
     247             : 
     248             :     char *p_headers;
     249         508 :     char *p_headers_end = headers + headers_buf_size;
     250             :     char buffer[INT_TO_STR_BUFFER_SIZE];
     251         508 :     const enum lwan_request_flags request_flags = request->flags;
     252         508 :     const enum lwan_connection_flags conn_flags = request->conn->flags;
     253         508 :     bool expires_override = !!(request->flags & (RESPONSE_NO_EXPIRES | REQUEST_HAS_QUERY_STRING));
     254             : 
     255         508 :     assert(request->global_response_headers);
     256             : 
     257         508 :     p_headers = headers;
     258             : 
     259         508 :     if (UNLIKELY(request_flags & REQUEST_IS_HTTP_1_0))
     260           2 :         APPEND_CONSTANT("HTTP/1.0 ");
     261             :     else
     262         506 :         APPEND_CONSTANT("HTTP/1.1 ");
     263         508 :     APPEND_STRING(lwan_http_status_as_string_with_code(status));
     264             : 
     265         508 :     if (LIKELY(!additional_headers))
     266          59 :         goto skip_additional_headers;
     267             : 
     268         449 :     if (LIKELY((status < HTTP_CLASS__CLIENT_ERROR))) {
     269             :         const struct lwan_key_value *header;
     270         443 :         bool date_override = false;
     271             : 
     272         895 :         for (header = additional_headers; header->key; header++) {
     273         904 :             STRING_SWITCH_L (header->key) {
     274           0 :             case STR4_INT_L('S', 'e', 'r', 'v'):
     275           0 :                 if (LIKELY(streq(header->key + 4, "er")))
     276           0 :                     continue;
     277           0 :                 break;
     278           0 :             case STR4_INT_L('D', 'a', 't', 'e'):
     279           0 :                 if (LIKELY(*(header->key + 4) == '\0'))
     280           0 :                     date_override = true;
     281           0 :                 break;
     282           0 :             case STR4_INT_L('E', 'x', 'p', 'i'):
     283           0 :                 if (LIKELY(streq(header->key + 4, "res")))
     284           0 :                     expires_override = true;
     285           0 :                 break;
     286             :             }
     287             : 
     288         452 :             RETURN_0_ON_OVERFLOW(4);
     289         452 :             APPEND_CHAR_NOCHECK('\r');
     290         452 :             APPEND_CHAR_NOCHECK('\n');
     291         452 :             APPEND_STRING(header->key);
     292         452 :             APPEND_CHAR_NOCHECK(':');
     293         452 :             APPEND_CHAR_NOCHECK(' ');
     294         452 :             APPEND_STRING(header->value);
     295             :         }
     296             : 
     297         443 :         if (date_override)
     298           0 :             goto skip_date_header;
     299           6 :     } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) {
     300             :         const struct lwan_key_value *header;
     301             : 
     302           3 :         for (header = additional_headers; header->key; header++) {
     303           3 :             if (streq(header->key, "WWW-Authenticate")) {
     304           3 :                 APPEND_CONSTANT("\r\nWWW-Authenticate: ");
     305           3 :                 APPEND_STRING(header->value);
     306           3 :                 break;
     307             :             }
     308             :         }
     309             :     }
     310             : 
     311           3 : skip_additional_headers:
     312         508 :     APPEND_CONSTANT("\r\nDate: ");
     313         508 :     APPEND_STRING_LEN(request->conn->thread->date.date, 29);
     314             : 
     315         508 : skip_date_header:
     316         508 :     if (UNLIKELY(conn_flags & CONN_IS_UPGRADE)) {
     317           1 :         APPEND_CONSTANT("\r\nConnection: Upgrade");
     318             :     } else {
     319         507 :         if (!(conn_flags & CONN_SENT_CONNECTION_HEADER)) {
     320         239 :             if (LIKELY(conn_flags & CONN_IS_KEEP_ALIVE))
     321         220 :                 APPEND_CONSTANT("\r\nConnection: keep-alive");
     322             :             else
     323          19 :                 APPEND_CONSTANT("\r\nConnection: close");
     324         239 :             request->conn->flags |= CONN_SENT_CONNECTION_HEADER;
     325             :         }
     326             : 
     327         507 :         if (LIKELY(request->response.mime_type)) {
     328         507 :             APPEND_CONSTANT("\r\nContent-Type: ");
     329         507 :             APPEND_STRING(request->response.mime_type);
     330             :         }
     331             : 
     332         507 :         if (!expires_override) {
     333         187 :             APPEND_CONSTANT("\r\nExpires: ");
     334         187 :             APPEND_STRING_LEN(request->conn->thread->date.expires, 29);
     335             :         }
     336             :     }
     337             : 
     338         508 :     const bool has_content_length = flags_has_content_length(request_flags);
     339         508 :     if (LIKELY(has_content_length)) {
     340         499 :         APPEND_CONSTANT("\r\nContent-Length: ");
     341         499 :         APPEND_UINT(lwan_strbuf_get_length(request->response.buffer));
     342             :     }
     343         508 :     if (UNLIKELY(has_uncommon_response_headers(request_flags))) {
     344         392 :         if (request_flags & REQUEST_ALLOW_CORS) {
     345           0 :             APPEND_CONSTANT(
     346             :                 "\r\nAccess-Control-Allow-Origin: *"
     347             :                 "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS"
     348             :                 "\r\nAccess-Control-Allow-Credentials: true"
     349             :                 "\r\nAccess-Control-Allow-Headers: Origin, Accept, "
     350             :                 "Content-Type");
     351             :         }
     352         392 :         if (!has_content_length && (request_flags & RESPONSE_CHUNKED_ENCODING)) {
     353           2 :             APPEND_CONSTANT("\r\nTransfer-Encoding: chunked");
     354             :         }
     355         392 :         if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) {
     356         390 :             APPEND_CONSTANT("\r\nX-Request-Id: ");
     357         390 :             RETURN_0_ON_OVERFLOW(16);
     358         390 :             uint64_t id = lwan_request_get_id(request);
     359        6630 :             for (int i = 60; i >= 0; i -= 4)
     360        6240 :                 APPEND_CHAR_NOCHECK("0123456789abcdef"[(id >> i) & 0xf]);
     361             :         }
     362         392 :         if (request_flags & REQUEST_WANTS_HSTS_HEADER) {
     363           0 :             APPEND_CONSTANT("\r\nStrict-Transport-Security: "
     364             :                             "max-age=31536000; includeSubdomains");
     365             :         }
     366             :     }
     367             : 
     368         508 :     APPEND_STRING_LEN(request->global_response_headers->value,
     369             :                       request->global_response_headers->len);
     370             : 
     371         508 :     return (size_t)(p_headers - headers);
     372             : }
     373             : 
     374             : #undef APPEND_CHAR
     375             : #undef APPEND_CHAR_NOCHECK
     376             : #undef APPEND_CONSTANT
     377             : #undef APPEND_STRING
     378             : #undef APPEND_STRING_LEN
     379             : #undef APPEND_UINT
     380             : #undef RETURN_0_ON_OVERFLOW
     381             : 
     382           0 : ALWAYS_INLINE size_t lwan_prepare_response_header(struct lwan_request *request,
     383             :                                                   enum lwan_http_status status,
     384             :                                                   char headers[],
     385             :                                                   size_t headers_buf_size)
     386             : {
     387         499 :     return lwan_prepare_response_header_full(
     388             :         request, status, headers, headers_buf_size, request->response.headers);
     389             : }
     390             : 
     391           2 : bool lwan_response_set_chunked_full(struct lwan_request *request,
     392             :                                     enum lwan_http_status status,
     393             :                                     const struct lwan_key_value *additional_headers)
     394             : {
     395             :     char buffer[DEFAULT_BUFFER_SIZE];
     396             :     size_t buffer_len;
     397             : 
     398           2 :     if (request->flags & RESPONSE_SENT_HEADERS)
     399           0 :         return false;
     400             : 
     401           2 :     request->flags |= RESPONSE_CHUNKED_ENCODING;
     402           2 :     buffer_len = lwan_prepare_response_header_full(request, status, buffer,
     403             :                                                    DEFAULT_BUFFER_SIZE,
     404             :                                                    additional_headers);
     405           2 :     if (UNLIKELY(!buffer_len))
     406           0 :         return false;
     407             : 
     408           2 :     request->flags |= RESPONSE_SENT_HEADERS;
     409           2 :     lwan_send(request, buffer, buffer_len, MSG_MORE);
     410             : 
     411           2 :     return true;
     412             : }
     413             : 
     414           2 : inline bool lwan_response_set_chunked(struct lwan_request *request,
     415             :                                       enum lwan_http_status status)
     416             : {
     417           2 :     return lwan_response_set_chunked_full(request, status,
     418             :                                           request->response.headers);
     419             : }
     420             : 
     421          16 : void lwan_response_send_chunk_full(struct lwan_request *request,
     422             :                                    struct lwan_strbuf *strbuf)
     423             : {
     424          16 :     if (!(request->flags & RESPONSE_SENT_HEADERS)) {
     425           2 :         if (UNLIKELY(!lwan_response_set_chunked(request, HTTP_OK)))
     426           2 :             return;
     427             :     }
     428             : 
     429          16 :     size_t buffer_len = lwan_strbuf_get_length(strbuf);
     430          16 :     if (UNLIKELY(!buffer_len)) {
     431             :         static const char last_chunk[] = "0\r\n\r\n";
     432           2 :         lwan_send(request, last_chunk, sizeof(last_chunk) - 1, 0);
     433           2 :         return;
     434             :     }
     435             : 
     436             :     char chunk_size[3 * sizeof(size_t) + 2];
     437             :     int converted_len =
     438          14 :         snprintf(chunk_size, sizeof(chunk_size), "%zx\r\n", buffer_len);
     439          14 :     if (UNLIKELY(converted_len < 0 ||
     440             :                  (size_t)converted_len >= sizeof(chunk_size))) {
     441           0 :         coro_yield(request->conn->coro, CONN_CORO_ABORT);
     442           0 :         __builtin_unreachable();
     443             :     }
     444          14 :     size_t chunk_size_len = (size_t)converted_len;
     445             : 
     446          28 :     struct iovec chunk_vec[] = {
     447             :         {.iov_base = chunk_size, .iov_len = chunk_size_len},
     448          14 :         {.iov_base = lwan_strbuf_get_buffer(strbuf), .iov_len = buffer_len},
     449             :         {.iov_base = "\r\n", .iov_len = 2},
     450             :     };
     451             : 
     452          14 :     lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec));
     453             : 
     454          14 :     lwan_strbuf_reset(strbuf);
     455             : }
     456             : 
     457          16 : void lwan_response_send_chunk(struct lwan_request *request)
     458             : {
     459          16 :     return lwan_response_send_chunk_full(request, request->response.buffer);
     460             : }
     461             : 
     462           0 : bool lwan_response_set_event_stream(struct lwan_request *request,
     463             :                                     enum lwan_http_status status)
     464             : {
     465             :     char buffer[DEFAULT_BUFFER_SIZE];
     466             :     size_t buffer_len;
     467             : 
     468           0 :     if (request->flags & RESPONSE_SENT_HEADERS)
     469           0 :         return false;
     470             : 
     471           0 :     request->response.mime_type = "text/event-stream";
     472           0 :     request->flags |= RESPONSE_NO_CONTENT_LENGTH;
     473           0 :     buffer_len = lwan_prepare_response_header(request, status, buffer,
     474             :                                               DEFAULT_BUFFER_SIZE);
     475           0 :     if (UNLIKELY(!buffer_len))
     476           0 :         return false;
     477             : 
     478           0 :     request->flags |= RESPONSE_SENT_HEADERS;
     479           0 :     lwan_send(request, buffer, buffer_len, MSG_MORE);
     480             : 
     481           0 :     return true;
     482             : }
     483             : 
     484           0 : void lwan_response_send_event(struct lwan_request *request, const char *event)
     485             : {
     486             :     struct iovec vec[6];
     487           0 :     int last = 0;
     488             : 
     489           0 :     if (!(request->flags & RESPONSE_SENT_HEADERS)) {
     490           0 :         if (UNLIKELY(!lwan_response_set_event_stream(request, HTTP_OK)))
     491           0 :             return;
     492             :     }
     493             : 
     494           0 :     if (event) {
     495           0 :         vec[last++] = (struct iovec){
     496             :             .iov_base = "event: ",
     497             :             .iov_len = sizeof("event: ") - 1,
     498             :         };
     499           0 :         vec[last++] = (struct iovec){
     500             :             .iov_base = (char *)event,
     501           0 :             .iov_len = strlen(event),
     502             :         };
     503           0 :         vec[last++] = (struct iovec){
     504             :             .iov_base = "\r\n",
     505             :             .iov_len = 2,
     506             :         };
     507             :     }
     508             : 
     509           0 :     size_t buffer_len = lwan_strbuf_get_length(request->response.buffer);
     510           0 :     if (buffer_len) {
     511           0 :         vec[last++] = (struct iovec){
     512             :             .iov_base = "data: ",
     513             :             .iov_len = sizeof("data: ") - 1,
     514             :         };
     515           0 :         vec[last++] = (struct iovec){
     516           0 :             .iov_base = lwan_strbuf_get_buffer(request->response.buffer),
     517             :             .iov_len = buffer_len,
     518             :         };
     519             :     }
     520             : 
     521           0 :     vec[last++] = (struct iovec){
     522             :         .iov_base = "\r\n\r\n",
     523             :         .iov_len = 4,
     524             :     };
     525             : 
     526           0 :     lwan_writev(request, vec, last);
     527             : 
     528           0 :     lwan_strbuf_reset(request->response.buffer);
     529           0 :     coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE);
     530             : }

Generated by: LCOV version 1.15-2-gb9d6727