libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

stream_process_reply.c (46332B)


      1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */
      2 /*
      3   This file is part of GNU libmicrohttpd.
      4   Copyright (C) 2014-2024 Evgeny Grin (Karlson2k)
      5   Copyright (C) 2007-2020 Daniel Pittman and Christian Grothoff
      6 
      7   GNU libmicrohttpd is free software; you can redistribute it and/or
      8   modify it under the terms of the GNU Lesser General Public
      9   License as published by the Free Software Foundation; either
     10   version 2.1 of the License, or (at your option) any later version.
     11 
     12   GNU libmicrohttpd is distributed in the hope that it will be useful,
     13   but WITHOUT ANY WARRANTY; without even the implied warranty of
     14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     15   Lesser General Public License for more details.
     16 
     17   Alternatively, you can redistribute GNU libmicrohttpd and/or
     18   modify it under the terms of the GNU General Public License as
     19   published by the Free Software Foundation; either version 2 of
     20   the License, or (at your option) any later version, together
     21   with the eCos exception, as follows:
     22 
     23     As a special exception, if other files instantiate templates or
     24     use macros or inline functions from this file, or you compile this
     25     file and link it with other works to produce a work based on this
     26     file, this file does not by itself cause the resulting work to be
     27     covered by the GNU General Public License. However the source code
     28     for this file must still be made available in accordance with
     29     section (3) of the GNU General Public License v2.
     30 
     31     This exception does not invalidate any other reasons why a work
     32     based on this file might be covered by the GNU General Public
     33     License.
     34 
     35   You should have received copies of the GNU Lesser General Public
     36   License and the GNU General Public License along with this library;
     37   if not, see <https://www.gnu.org/licenses/>.
     38 */
     39 
     40 /**
     41  * @file src/mhd2/stream_process_reply.h
     42  * @brief  The implementation of internal functions for forming and sending
     43  *         replies for requests
     44  * @author Karlson2k (Evgeny Grin)
     45  *
     46  * Based on the MHD v0.x code by Daniel Pittman, Christian Grothoff and other
     47  * contributors.
     48  */
     49 
     50 #include "mhd_sys_options.h"
     51 
     52 #include "sys_bool_type.h"
     53 #include "sys_base_types.h"
     54 
     55 #include "mhd_assert.h"
     56 #include "mhd_unreachable.h"
     57 
     58 #include <string.h>
     59 #ifdef HAVE_TIME_H
     60 #  include <time.h>
     61 #endif
     62 
     63 #include "mhd_daemon.h"
     64 #include "mhd_response.h"
     65 #include "mhd_reply.h"
     66 #include "mhd_connection.h"
     67 
     68 #include "daemon_logger.h"
     69 
     70 #include "mhd_str.h"
     71 #include "http_status_str.h"
     72 #include "stream_process_reply.h"
     73 #include "stream_funcs.h"
     74 #include "request_get_value.h"
     75 #ifdef MHD_SUPPORT_AUTH_DIGEST
     76 #  include "mhd_digest_auth_data.h"
     77 #  include "auth_digest.h"
     78 #endif
     79 
     80 #include "mhd_read_file.h"
     81 
     82 #include "mhd_public_api.h"
     83 
     84 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void
     85 mhd_stream_call_dcc_cleanup_if_needed (struct MHD_Connection *restrict c)
     86 {
     87   if (mhd_DCC_ACTION_CONTINUE != c->rp.app_act.act)
     88     return;
     89   if (NULL == c->rp.app_act.data.cntnue.iov_data)
     90     return;
     91 
     92   mhd_assert (mhd_RESPONSE_CONTENT_DATA_CALLBACK == \
     93               c->rp.response->cntn_dtype);
     94 
     95   if (NULL != c->rp.app_act.data.cntnue.iov_data->iov_fcb)
     96   {
     97     c->rp.app_act.data.cntnue.iov_data->iov_fcb (
     98       c->rp.app_act.data.cntnue.iov_data->iov_fcb_cls);
     99   }
    100 
    101   c->rp.app_act.data.cntnue.iov_data = NULL;
    102 }
    103 
    104 
    105 /**
    106  * This enum type describes requirements for reply body and reply bode-specific
    107  * headers (namely Content-Length, Transfer-Encoding).
    108  */
    109 enum replyBodyUse
    110 {
    111   /**
    112    * No reply body allowed.
    113    * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are
    114    * not allowed as well.
    115    */
    116   RP_BODY_NONE = 0,
    117 
    118   /**
    119    * Do not send reply body.
    120    * Reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked' are
    121    * allowed, but optional.
    122    */
    123   RP_BODY_HEADERS_ONLY = 1,
    124 
    125   /**
    126    * Send reply body and
    127    * reply body headers 'Content-Length:' or 'Transfer-Encoding: chunked'.
    128    * Reply body headers are required.
    129    */
    130   RP_BODY_SEND = 2
    131 };
    132 
    133 
    134 /**
    135  * Is it allowed to reuse the connection?
    136  * The TCP stream can be reused for the next requests if the connection
    137  * is HTTP 1.1 and the "Connection" header either does not exist or
    138  * is not set to "close", or if the connection is HTTP 1.0 and the
    139  * "Connection" header is explicitly set to "keep-alive".
    140  * If no HTTP version is specified (or if it is not 1.0 or 1.1), the connection
    141  * is definitively closed.  If the "Connection" header is not exactly "close"
    142  * or "keep-alive", connection is reused if is it HTTP/1.1.
    143  * If response has HTTP/1.0 flag or has "Connection: close" header
    144  * then connection must be closed.
    145  * If full request has not been read then connection must be closed
    146  * as well as more client data may be sent.
    147  *
    148  * @param c the connection to check for re-use
    149  * @return mhd_CONN_KEEPALIVE_POSSIBLE if (based on the request and
    150  *         the response) a connection could be reused,
    151  *         MHD_CONN_MUST_CLOSE if connection must be closed after sending
    152  *         complete reply,
    153  *         mhd_CONN_MUST_UPGRADE if connection must be upgraded.
    154  */
    155 static MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnReuse
    156 get_conn_reuse (struct MHD_Connection *c)
    157 {
    158   const struct MHD_Response *const restrict rp = c->rp.response;
    159 
    160   mhd_assert (NULL != rp);
    161   if (mhd_CONN_MUST_CLOSE == c->conn_reuse)
    162     return mhd_CONN_MUST_CLOSE;
    163 
    164   mhd_assert ( (! c->stop_with_error) || (c->discard_request));
    165   if ((c->sk.state.rmt_shut_wr) || (c->discard_request))
    166     return mhd_CONN_MUST_CLOSE;
    167 
    168   if (rp->cfg.close_forced)
    169     return mhd_CONN_MUST_CLOSE;
    170 
    171   mhd_assert ((MHD_SIZE_UNKNOWN != rp->cntn_size) || \
    172               (! rp->cfg.mode_1_0));
    173 
    174   if (! MHD_HTTP_VERSION_IS_SUPPORTED (c->rq.http_ver))
    175     return mhd_CONN_MUST_CLOSE;
    176 
    177   if (rp->cfg.mode_1_0 &&
    178       ! mhd_stream_has_header_token_st (c,
    179                                         MHD_HTTP_HEADER_CONNECTION,
    180                                         "keep-alive"))
    181     return mhd_CONN_MUST_CLOSE;
    182 
    183 #if 0 // def MHD_SUPPORT_UPGRADE // TODO: Implement upgrade support
    184   /* TODO: Move below the next check when MHD stops closing connections
    185    * when response is queued in first callback */
    186   if (NULL != r->upgrade_handler)
    187   {
    188     /* No "close" token is enforced by 'add_response_header_connection()' */
    189     mhd_assert (0 == (r->flags_auto & MHD_RAF_HAS_CONNECTION_CLOSE));
    190     /* Valid HTTP version is enforced by 'MHD_queue_response()' */
    191     mhd_assert (MHD_IS_HTTP_VER_SUPPORTED (c->rq.http_ver));
    192     mhd_assert (! c->stop_with_error);
    193     return mhd_CONN_MUST_UPGRADE;
    194   }
    195 #endif /* MHD_SUPPORT_UPGRADE */
    196 
    197   return mhd_CONN_KEEPALIVE_POSSIBLE;
    198 }
    199 
    200 
    201 /**
    202  * Check whether reply body must be used.
    203  *
    204  * If reply body is needed, it could be zero-sized.
    205  *
    206  * @param c the connection to check
    207  * @param rcode the response code
    208  * @return enum value indicating whether response body can be used and
    209  *         whether response body length headers are allowed or required.
    210  * @sa is_reply_body_header_needed()
    211  */
    212 static enum replyBodyUse
    213 is_reply_body_needed (struct MHD_Connection *restrict c,
    214                       uint_fast16_t rcode)
    215 {
    216   mhd_assert (100 <= rcode);
    217   mhd_assert (999 >= rcode);
    218 
    219   if (199 >= rcode)
    220     return RP_BODY_NONE;
    221 
    222   if (MHD_HTTP_STATUS_NO_CONTENT == rcode)
    223     return RP_BODY_NONE;
    224 
    225 #if 0
    226   /* This check is not needed as upgrade handler is used only with code 101 */
    227 #ifdef MHD_SUPPORT_UPGRADE
    228   if (NULL != rp.response->upgrade_handler)
    229     return RP_BODY_NONE;
    230 #endif /* MHD_SUPPORT_UPGRADE */
    231 #endif
    232 
    233 #if 0
    234   /* CONNECT is not supported by MHD */
    235   /* Successful responses for connect requests are filtered by
    236    * MHD_queue_response() */
    237   if ( (mhd_HTTP_METHOD_CONNECT == c->rq.http_mthd) &&
    238        (2 == rcode / 100) )
    239     return false; /* Actually pass-through CONNECT is not supported by MHD */
    240 #endif
    241 
    242   /* Reply body headers could be used.
    243    * Check whether reply body itself must be used. */
    244 
    245   if (mhd_HTTP_METHOD_HEAD == c->rq.http_mthd)
    246     return RP_BODY_HEADERS_ONLY;
    247 
    248   if (MHD_HTTP_STATUS_NOT_MODIFIED == rcode)
    249     return RP_BODY_HEADERS_ONLY;
    250 
    251   /* Reply body must be sent.
    252    * The body may have zero length, but body size must be indicated by
    253    * headers ('Content-Length:' or 'Transfer-Encoding: chunked'). */
    254   return RP_BODY_SEND;
    255 }
    256 
    257 
    258 /**
    259  * Setup connection reply properties.
    260  *
    261  * Reply properties include presence of reply body, transfer-encoding
    262  * type and other.
    263  *
    264  * @param c the connection to process
    265  */
    266 static MHD_FN_PAR_NONNULL_ALL_ void
    267 setup_reply_properties (struct MHD_Connection *restrict c)
    268 {
    269   struct MHD_Response *const restrict r = c->rp.response;  /**< a short alias */
    270   enum replyBodyUse use_rp_body;
    271   bool use_chunked;
    272   bool end_by_closing;
    273 
    274   mhd_assert (NULL != r);
    275 
    276   /* ** Adjust reply properties ** */
    277 
    278   c->conn_reuse = get_conn_reuse (c);
    279   use_rp_body = is_reply_body_needed (c, r->sc);
    280   c->rp.props.send_reply_body = (use_rp_body > RP_BODY_HEADERS_ONLY);
    281   c->rp.props.use_reply_body_headers = (use_rp_body >= RP_BODY_HEADERS_ONLY);
    282 
    283 #if 0 // def MHD_SUPPORT_UPGRADE // TODO: upgrade support
    284   mhd_assert ( (NULL == r->upgrade_handler) ||
    285                (RP_BODY_NONE == use_rp_body) );
    286 #endif /* MHD_SUPPORT_UPGRADE */
    287 
    288   use_chunked = false;
    289   end_by_closing = false;
    290   if (c->rp.props.use_reply_body_headers)
    291   {
    292     if (r->cfg.chunked)
    293     {
    294       mhd_assert (! r->cfg.mode_1_0);
    295       use_chunked = (MHD_HTTP_VERSION_1_1 == c->rq.http_ver);
    296     }
    297     if ((MHD_SIZE_UNKNOWN == r->cntn_size) &&
    298         (! use_chunked) &&
    299         (c->rp.props.send_reply_body))
    300     {
    301       /* End of the stream is indicated by closure */
    302       end_by_closing = true;
    303     }
    304   }
    305 
    306   if (end_by_closing)
    307   {
    308     mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse);
    309     /* End of the stream is indicated by closure */
    310     c->conn_reuse = mhd_CONN_MUST_CLOSE;
    311   }
    312 
    313   c->rp.props.chunked = use_chunked;
    314   c->rp.props.end_by_closing = end_by_closing;
    315 
    316   if ((! c->rp.props.send_reply_body) || (0 == r->cntn_size))
    317     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
    318   else if (c->rp.props.chunked)
    319     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF;
    320   else
    321   {
    322     switch (r->cntn_dtype)
    323     {
    324     case mhd_RESPONSE_CONTENT_DATA_BUFFER:
    325       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_RESP_BUF;
    326       break;
    327     case mhd_RESPONSE_CONTENT_DATA_IOVEC:
    328       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_IOV;
    329       break;
    330     case mhd_RESPONSE_CONTENT_DATA_FILE:
    331       if (mhd_C_HAS_TLS (c))
    332       {
    333         c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF;
    334         break;
    335       }
    336 #ifdef mhd_USE_SENDFILE
    337       if (r->cntn.file.use_sf)
    338       {
    339         c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_FILE;
    340         break;
    341       }
    342 #endif
    343       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF;
    344       break;
    345     case mhd_RESPONSE_CONTENT_DATA_CALLBACK:
    346       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF;
    347       break;
    348     case mhd_RESPONSE_CONTENT_DATA_INVALID:
    349     default:
    350       mhd_UNREACHABLE ();
    351       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
    352       break;
    353     }
    354   }
    355 
    356 #ifndef NDEBUG
    357   c->rp.props.set = true;
    358 #endif /* _DEBUG */
    359 }
    360 
    361 
    362 /**
    363  * Check whether queued response is suitable for @a connection.
    364  * @param c the connection to check
    365  */
    366 static void
    367 check_connection_reply (struct MHD_Connection *restrict c)
    368 {
    369   struct MHD_Response *const restrict r = c->rp.response;  /**< a short alias */
    370 
    371   mhd_assert (c->rp.props.set);
    372 
    373   if ( (! c->rp.props.use_reply_body_headers) &&
    374        (0 != r->cntn_size) )
    375   {
    376     mhd_LOG_PRINT (c->daemon, MHD_SC_REPLY_NOT_EMPTY_RESPONSE,
    377                    mhd_LOG_FMT ("This reply with response code %u " \
    378                                 "cannot use reply content. Non-empty " \
    379                                 "response content is ignored and not used."),
    380                    (unsigned) (c->rp.response->sc));
    381   }
    382   if ( (! c->rp.props.use_reply_body_headers) &&
    383        (r->cfg.cnt_len_by_app) )
    384   {
    385     mhd_LOG_PRINT (c->daemon, MHD_SC_REPLY_CONTENT_LENGTH_NOT_ALLOWED,
    386                    mhd_LOG_FMT ("This reply with response code %u " \
    387                                 "cannot use reply content. Application " \
    388                                 "defined \"Content-Length\" header " \
    389                                 "violates HTTP specification."),
    390                    (unsigned) (c->rp.response->sc));
    391   }
    392 }
    393 
    394 
    395 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_
    396 MHD_FN_PAR_OUT_ (1) bool
    397 mhd_build_date_str (char date[MHD_FN_PAR_FIX_ARR_SIZE_ (29)])
    398 {
    399   static const char *const days[] = {
    400     "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
    401   };
    402   static const char *const mons[] = {
    403     "Jan", "Feb", "Mar", "Apr", "May", "Jun",
    404     "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
    405   };
    406   static const size_t buf_len = 29;
    407   struct tm now;
    408   time_t t;
    409   const char *src;
    410 #if ! defined(HAVE_C11_GMTIME_S) && ! defined(HAVE_W32_GMTIME_S) && \
    411   ! defined(HAVE_GMTIME_R)
    412   struct tm *pNow;
    413 #endif
    414 
    415   if ((time_t) -1 == time (&t))
    416     return false;
    417 #if defined(HAVE_GMTIME_R)
    418   if (NULL == gmtime_r (&t,
    419                         &now))
    420     return false;
    421 #elif defined(HAVE_C11_GMTIME_S)
    422   if (NULL == gmtime_s (&t,
    423                         &now))
    424     return false;
    425 #elif defined(HAVE_W32_GMTIME_S)
    426   if (0 != gmtime_s (&now,
    427                      &t))
    428     return false;
    429 #else
    430   pNow = gmtime (&t);
    431   if (NULL == pNow)
    432     return false;
    433   now = *pNow;
    434 #endif
    435 
    436   /* Day of the week */
    437   src = days[now.tm_wday % 7];
    438   date[0] = src[0];
    439   date[1] = src[1];
    440   date[2] = src[2];
    441   date[3] = ',';
    442   date[4] = ' ';
    443   /* Day of the month */
    444   if (2 != mhd_uint8_to_str_pad ((uint8_t) now.tm_mday, 2,
    445                                  date + 5, buf_len - 5))
    446     return false;
    447   date[7] = ' ';
    448   /* Month */
    449   src = mons[now.tm_mon % 12];
    450   date[8] = src[0];
    451   date[9] = src[1];
    452   date[10] = src[2];
    453   date[11] = ' ';
    454   /* Year */
    455   if (4 != mhd_uint16_to_str ((uint_least16_t) (1900 + now.tm_year), date + 12,
    456                               buf_len - 12))
    457     return false;
    458   date[16] = ' ';
    459   /* Time */
    460   mhd_uint8_to_str_pad ((uint8_t) now.tm_hour, 2, date + 17, buf_len - 17);
    461   date[19] = ':';
    462   mhd_uint8_to_str_pad ((uint8_t) now.tm_min, 2, date + 20, buf_len - 20);
    463   date[22] = ':';
    464   mhd_uint8_to_str_pad ((uint8_t) now.tm_sec, 2, date + 23, buf_len - 23);
    465   date[25] = ' ';
    466   date[26] = 'G';
    467   date[27] = 'M';
    468   date[28] = 'T';
    469 
    470   return true;
    471 }
    472 
    473 
    474 /**
    475  * Produce HTTP DATE header.
    476  * Result is always 37 bytes long (plus one terminating null).
    477  *
    478  * @param[out] header where to write the header, with
    479  *             at least 38 bytes available space.
    480  */
    481 static MHD_FN_PAR_NONNULL_ALL_
    482 MHD_FN_PAR_OUT_ (1) bool
    483 get_date_header (char *header)
    484 {
    485   header[0] = 'D';
    486   header[1] = 'a';
    487   header[2] = 't';
    488   header[3] = 'e';
    489   header[4] = ':';
    490   header[5] = ' ';
    491   if (! mhd_build_date_str (header + 6))
    492   {
    493     header[0] = 0;
    494     return false;
    495   }
    496   header[35] = '\r';
    497   header[36] = '\n';
    498   header[37] = 0;
    499   return true;
    500 }
    501 
    502 
    503 /**
    504  * Append data to the buffer if enough space is available,
    505  * update position.
    506  * @param[out] buf the buffer to append data to
    507  * @param[in,out] ppos the pointer to position in the @a buffer
    508  * @param buf_size the size of the @a buffer
    509  * @param append the data to append
    510  * @param append_size the size of the @a append
    511  * @return true if data has been added and position has been updated,
    512  *         false if not enough space is available
    513  */
    514 static MHD_FN_PAR_NONNULL_ALL_ bool
    515 buffer_append (char *buf,
    516                size_t *ppos,
    517                size_t buf_size,
    518                const char *append,
    519                size_t append_size)
    520 {
    521   mhd_assert (NULL != buf); /* Mute static analyzer */
    522   if (buf_size < *ppos + append_size)
    523     return false;
    524   memcpy (buf + *ppos, append, append_size);
    525   *ppos += append_size;
    526   return true;
    527 }
    528 
    529 
    530 /**
    531  * Add user-defined headers from response object to
    532  * the text buffer.
    533  *
    534  * @param buf the buffer to add headers to
    535  * @param ppos the pointer to the position in the @a buf
    536  * @param buf_size the size of the @a buf
    537  * @param r the response
    538  * @param filter_content_len skip "Content-Length" header if any
    539  * @param add_close add "close" token to the
    540  *                  "Connection:" header (if any), ignored if no "Connection:"
    541  *                  header was added by user or if "close" token is already
    542  *                  present in "Connection:" header
    543  * @param add_keep_alive add "Keep-Alive" token to the
    544  *                       "Connection:" header (if any)
    545  * @return true if succeed,
    546  *         false if buffer is too small
    547  */
    548 static MHD_FN_PAR_NONNULL_ALL_ bool
    549 add_user_headers (char *restrict buf,
    550                   size_t *restrict ppos,
    551                   size_t buf_size,
    552                   struct MHD_Response *restrict r,
    553                   bool filter_content_len,
    554                   bool add_close,
    555                   bool add_keep_alive)
    556 {
    557   struct mhd_ResponseHeader *hdr; /**< Iterates through User-specified headers */
    558   size_t el_size; /**< the size of current element to be added to the @a buf */
    559 
    560   mhd_assert (! add_close || ! add_keep_alive);
    561   mhd_assert (! add_keep_alive || ! add_close);
    562 
    563   if (r->cfg.has_hdr_conn)
    564   {
    565     add_close = false;          /* No such header */
    566     add_keep_alive = false;     /* No such header */
    567   }
    568 
    569   for (hdr = mhd_DLINKEDL_GET_FIRST (r, headers);
    570        NULL != hdr;
    571        hdr = mhd_DLINKEDL_GET_NEXT (hdr, headers))
    572   {
    573     size_t initial_pos = *ppos;
    574 
    575     if (filter_content_len)
    576     { /* Need to filter-out "Content-Length" */
    577       if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONTENT_LENGTH, \
    578                                        hdr->name.cstr,
    579                                        hdr->name.len))
    580       {
    581         /* Reset filter flag  */
    582         filter_content_len = false;
    583         continue; /* Skip "Content-Length" header */
    584       }
    585     }
    586 
    587     /* Add user header */
    588     el_size = hdr->name.len + 2 + hdr->value.len + 2;
    589     if (buf_size < *ppos + el_size)
    590       return false;
    591     memcpy (buf + *ppos, hdr->name.cstr, hdr->name.len);
    592     (*ppos) += hdr->name.len;
    593     buf[(*ppos)++] = ':';
    594     buf[(*ppos)++] = ' ';
    595     if (add_close || add_keep_alive)
    596     {
    597       /* "Connection:" header must be always the first one */
    598       mhd_assert (mhd_str_equal_caseless_n (hdr->name.cstr, \
    599                                             MHD_HTTP_HEADER_CONNECTION, \
    600                                             hdr->name.len));
    601 
    602       if (add_close)
    603       {
    604         el_size += mhd_SSTR_LEN ("close, ");
    605         if (buf_size < initial_pos + el_size)
    606           return false;
    607         memcpy (buf + *ppos, "close, ",
    608                 mhd_SSTR_LEN ("close, "));
    609         *ppos += mhd_SSTR_LEN ("close, ");
    610       }
    611       else
    612       {
    613         el_size += mhd_SSTR_LEN ("Keep-Alive, ");
    614         if (buf_size < initial_pos + el_size)
    615           return false;
    616         memcpy (buf + *ppos, "Keep-Alive, ",
    617                 mhd_SSTR_LEN ("Keep-Alive, "));
    618         *ppos += mhd_SSTR_LEN ("Keep-Alive, ");
    619       }
    620       add_close = false;
    621       add_keep_alive = false;
    622     }
    623     if (0 != hdr->value.len)
    624       memcpy (buf + *ppos, hdr->value.cstr, hdr->value.len);
    625     *ppos += hdr->value.len;
    626     buf[(*ppos)++] = '\r';
    627     buf[(*ppos)++] = '\n';
    628     mhd_assert (initial_pos + el_size == (*ppos));
    629   }
    630   return true;
    631 }
    632 
    633 
    634 /**
    635  * Append static string to the buffer if enough space is available,
    636  * update position.
    637  * @param[out] buf the buffer to append data to
    638  * @param[in,out] ppos the pointer to position in the @a buffer
    639  * @param buf_size the size of the @a buffer
    640  * @param str the static string to append
    641  * @return true if data has been added and position has been updated,
    642  *         false if not enough space is available
    643  */
    644 #define buffer_append_s(buf,ppos,buf_size,str) \
    645         buffer_append (buf,ppos,buf_size,str, mhd_SSTR_LEN (str))
    646 
    647 
    648 /**
    649  * Append MHD_String to the buffer if enough space is available,
    650  * update position.
    651  * @param[out] buf the buffer to append data to
    652  * @param[in,out] ppos the pointer to position in the @a buffer
    653  * @param buf_size the size of the @a buffer
    654  * @param pmhdstr the pointer to string to append
    655  * @return true if data has been added and position has been updated,
    656  *         false if not enough space is available
    657  */
    658 #define buffer_append_mstr(buf,ppos,buf_size,pmhdstr) \
    659         buffer_append ((buf),(ppos),(buf_size), \
    660                        (pmhdstr)->cstr, (pmhdstr)->len)
    661 
    662 /**
    663  * Allocate the connection's write buffer and fill it with all of the
    664  * headers from the response.
    665  * Inner version of the function.
    666  *
    667  * @param c the connection to process
    668  * @return 'true' if state has been update,
    669  *         'false' if connection is going to be aborted
    670  */
    671 static MHD_FN_PAR_NONNULL_ALL_ bool
    672 build_header_response_inn (struct MHD_Connection *restrict c)
    673 {
    674   struct MHD_Response *const restrict r = c->rp.response;
    675   char *restrict buf;                            /**< the output buffer */
    676   size_t pos;                                    /**< append offset in the @a buf */
    677   size_t buf_size;                               /**< the size of the @a buf */
    678   size_t el_size;                                /**< the size of current element to be added to the @a buf */
    679   uint_fast16_t rcode;                           /**< the response code */
    680   bool use_conn_close;                           /**< Use "Connection: close" header */
    681   bool use_conn_k_alive;                         /**< Use "Connection: Keep-Alive" header */
    682 
    683   mhd_assert (NULL != r);
    684 
    685   /* ** Adjust response properties ** */
    686   setup_reply_properties (c);
    687 
    688   mhd_assert (c->rp.props.set);
    689   mhd_assert ((mhd_CONN_MUST_CLOSE == c->conn_reuse) || \
    690               (mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse) || \
    691               (mhd_CONN_MUST_UPGRADE == c->conn_reuse));
    692 #if 0 // def MHD_SUPPORT_UPGRADE // TODO: upgrade support
    693   mhd_assert ((NULL == r->upgrade_handler) || \
    694               (mhd_CONN_MUST_UPGRADE == c->keepalive));
    695 #else  /* ! MHD_SUPPORT_UPGRADE */
    696   mhd_assert (mhd_CONN_MUST_UPGRADE != c->conn_reuse);
    697 #endif /* ! MHD_SUPPORT_UPGRADE */
    698   mhd_assert ((! c->rp.props.chunked) || c->rp.props.use_reply_body_headers);
    699   mhd_assert ((! c->rp.props.send_reply_body) || \
    700               c->rp.props.use_reply_body_headers);
    701   mhd_assert ((! c->rp.props.end_by_closing) || \
    702               (mhd_CONN_MUST_CLOSE == c->conn_reuse));
    703 #if 0 // def MHD_SUPPORT_UPGRADE  // TODO: upgrade support
    704   mhd_assert (NULL == r->upgrade_handler || \
    705               ! c->rp.props.use_reply_body_headers);
    706 #endif /* MHD_SUPPORT_UPGRADE */
    707 
    708   check_connection_reply (c);
    709 
    710   rcode = (uint_fast16_t) c->rp.response->sc;
    711   if (mhd_CONN_MUST_CLOSE == c->conn_reuse)
    712   {
    713     /* The closure of connection must be always indicated by header
    714      * to avoid hung connections */
    715     use_conn_close = true;
    716     use_conn_k_alive = false;
    717   }
    718   else if (mhd_CONN_KEEPALIVE_POSSIBLE == c->conn_reuse)
    719   {
    720     mhd_assert (! r->cfg.mode_1_0);
    721     use_conn_close = false;
    722     /* Add "Connection: keep-alive" if request is HTTP/1.0 or
    723      * if reply is HTTP/1.0
    724      * For HTTP/1.1 add header only if explicitly requested by app
    725      * (by response flag), as "Keep-Alive" is default for HTTP/1.1. */
    726     if (r->cfg.mode_1_0 ||
    727         (MHD_HTTP_VERSION_1_0 == c->rq.http_ver))
    728       use_conn_k_alive = true;
    729     else
    730       use_conn_k_alive = false;
    731   }
    732   else
    733   {
    734     use_conn_close = false;
    735     use_conn_k_alive = false;
    736   }
    737 
    738   /* ** Actually build the response header ** */
    739 
    740   /* Get all space available */
    741   mhd_stream_maximize_write_buffer (c);
    742   buf = c->write_buffer;
    743   pos = c->write_buffer_append_offset;
    744   buf_size = c->write_buffer_size;
    745   if (0 == buf_size)
    746     return false;
    747   mhd_assert (NULL != buf);
    748 
    749   // TODO: use pre-calculated header size
    750   /* * The status line * */
    751 
    752   /* The HTTP version */
    753   if (! c->rp.responseIcy)
    754   { /* HTTP reply */
    755     if (! r->cfg.mode_1_0)
    756     { /* HTTP/1.1 reply */
    757       /* Use HTTP/1.1 responses for HTTP/1.0 clients.
    758        * See https://datatracker.ietf.org/doc/html/rfc7230#section-2.6 */
    759       if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_1_STR))
    760         return false;
    761     }
    762     else
    763     { /* HTTP/1.0 reply */
    764       if (! buffer_append_s (buf, &pos, buf_size, MHD_HTTP_VERSION_1_0_STR))
    765         return false;
    766     }
    767   }
    768   else
    769   { /* ICY reply */
    770     if (! buffer_append_s (buf, &pos, buf_size, "ICY"))
    771       return false;
    772   }
    773 
    774   /* The response code */
    775   if (buf_size < pos + 5) /* space + code + space */
    776     return false;
    777   buf[pos++] = ' ';
    778   pos += mhd_uint16_to_str ((uint16_t) rcode, buf + pos,
    779                             buf_size - pos);
    780   buf[pos++] = ' ';
    781 
    782   /* The reason phrase */
    783   if (1)
    784   {
    785     const struct MHD_String *stat_str;
    786     stat_str = mhd_HTTP_status_code_to_string_int (rcode);
    787     mhd_assert (0 != stat_str->len);
    788     if (! buffer_append (buf, &pos, buf_size,
    789                          stat_str->cstr,
    790                          stat_str->len))
    791       return false;
    792   }
    793   /* The linefeed */
    794   if (buf_size < pos + 2)
    795     return false;
    796   buf[pos++] = '\r';
    797   buf[pos++] = '\n';
    798 
    799   /* * The headers * */
    800 
    801   /* A special custom header */
    802   if (0 != r->special_resp.spec_hdr_len)
    803   {
    804     mhd_assert (r->cfg.int_err_resp);
    805     if (buf_size < pos + r->special_resp.spec_hdr_len + 2)
    806       return false;
    807     memcpy (buf + pos,
    808             r->special_resp.spec_hdr,
    809             r->special_resp.spec_hdr_len);
    810     buf[pos++] = '\r';
    811     buf[pos++] = '\n';
    812   }
    813 
    814   /* Main automatic headers */
    815 
    816   /* The "Date:" header */
    817   if ( (! r->cfg.has_hdr_date) &&
    818        (! c->daemon->req_cfg.suppress_date) )
    819   {
    820     /* Additional byte for unused zero-termination */
    821     if (buf_size < pos + 38)
    822       return false;
    823     if (get_date_header (buf + pos))
    824       pos += 37;
    825   }
    826   /* The "Connection:" header */
    827   mhd_assert (! use_conn_close || ! use_conn_k_alive);
    828   mhd_assert (! use_conn_k_alive || ! use_conn_close);
    829   if (! r->cfg.has_hdr_conn)
    830   {
    831     if (use_conn_close)
    832     {
    833       if (! buffer_append_s (buf, &pos, buf_size,
    834                              MHD_HTTP_HEADER_CONNECTION ": close\r\n"))
    835         return false;
    836     }
    837     else if (use_conn_k_alive)
    838     {
    839       if (! buffer_append_s (buf, &pos, buf_size,
    840                              MHD_HTTP_HEADER_CONNECTION ": Keep-Alive\r\n"))
    841         return false;
    842     }
    843     use_conn_close = false;
    844     use_conn_k_alive = false;
    845   }
    846 
    847   /* Special headers */
    848 #ifdef MHD_SUPPORT_AUTH_DIGEST
    849   if (mhd_RESP_HAS_AUTH_DIGEST (r))
    850   {
    851     char noncestr[mhd_AUTH_DIGEST_NONCE_LEN];
    852     const struct mhd_RespAuthDigestHeader *dg_hdr;
    853     if (! mhd_auth_digest_get_new_nonce (c,
    854                                          noncestr))
    855     {
    856       mhd_STREAM_ABORT (c,
    857                         mhd_CONN_CLOSE_NONCE_ERROR,
    858                         "Failed to generate a new nonce for Digest Auth.");
    859       return false;
    860     }
    861     for (dg_hdr = mhd_DLINKEDL_GET_FIRST (r, auth_d_hdrs);
    862          NULL != dg_hdr;
    863          dg_hdr = mhd_DLINKEDL_GET_NEXT (dg_hdr, auth_d_hdrs))
    864     {
    865       size_t nonce_pos;
    866       nonce_pos = pos + dg_hdr->nonce_pos;
    867       if (! buffer_append_mstr (buf, &pos, buf_size, \
    868                                 &(dg_hdr->hdr)))
    869         return false;
    870       memcpy (buf + nonce_pos,
    871               noncestr,
    872               sizeof(noncestr));
    873     }
    874   }
    875 #endif /* MHD_SUPPORT_AUTH_DIGEST */
    876 
    877   /* User-defined headers */
    878 
    879   if (! add_user_headers (buf, &pos, buf_size, r,
    880                           ! c->rp.props.use_reply_body_headers,
    881                           use_conn_close,
    882                           use_conn_k_alive))
    883     return false;
    884 
    885   /* Other automatic headers */
    886 
    887   if (c->rp.props.use_reply_body_headers)
    888   {
    889     /* Body-specific headers */
    890 
    891     if (c->rp.props.chunked)
    892     { /* Chunked encoding is used */
    893       mhd_assert (! c->rp.props.end_by_closing);
    894       if (! buffer_append_s (buf, &pos, buf_size,
    895                              MHD_HTTP_HEADER_TRANSFER_ENCODING ": " \
    896                              "chunked\r\n"))
    897         return false;
    898     }
    899     else /* Chunked encoding is not used */
    900     {
    901       if ((MHD_SIZE_UNKNOWN != r->cntn_size) &&
    902           (! c->rp.props.end_by_closing) &&
    903           (! r->cfg.chunked) &&
    904           (! r->cfg.head_only))
    905       { /* The size is known and can be indicated by the header */
    906         if (! r->cfg.cnt_len_by_app)
    907         { /* The response does not have app-defined "Content-Length" header */
    908           if (! buffer_append_s (buf, &pos, buf_size,
    909                                  MHD_HTTP_HEADER_CONTENT_LENGTH ": "))
    910             return false;
    911           el_size = mhd_uint64_to_str (r->cntn_size,
    912                                        buf + pos,
    913                                        buf_size - pos);
    914           if (0 == el_size)
    915             return false;
    916           pos += el_size;
    917 
    918           if (buf_size < pos + 2)
    919             return false;
    920           buf[pos++] = '\r';
    921           buf[pos++] = '\n';
    922         }
    923       }
    924       else
    925       {
    926         mhd_assert ((! c->rp.props.send_reply_body) || \
    927                     (mhd_CONN_MUST_CLOSE == c->conn_reuse));
    928         (void) 0;
    929       }
    930     }
    931   }
    932 
    933   /* * Header termination * */
    934   if (buf_size < pos + 2)
    935     return false;
    936   buf[pos++] = '\r';
    937   buf[pos++] = '\n';
    938 
    939   c->write_buffer_append_offset = pos;
    940   return true;
    941 }
    942 
    943 
    944 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
    945 mhd_stream_build_header_response (struct MHD_Connection *restrict c)
    946 {
    947   if (! build_header_response_inn (c))
    948   {
    949 #ifdef MHD_SUPPORT_AUTH_DIGEST
    950     if (mhd_HTTP_STAGE_PRE_CLOSING <= c->stage)
    951       return false; /* Already started closing */
    952 #else  /* ! MHD_SUPPORT_AUTH_DIGEST */
    953     mhd_assert (mhd_HTTP_STAGE_START_REPLY == c->stage);
    954 #endif /* ! MHD_SUPPORT_AUTH_DIGEST */
    955     mhd_STREAM_ABORT (c,
    956                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
    957                       "No memory in the pool for the reply headers.");
    958     return false;
    959   }
    960   c->stage = mhd_HTTP_STAGE_HEADERS_SENDING;
    961   return true;
    962 }
    963 
    964 
    965 /**
    966  * Pre-process DCC action provided by application.
    967  * 'abort' and 'suspend' actions are fully processed,
    968  * 'continue' and 'finish' actions needs to be processed by the caller.
    969  * @param c the stream to use
    970  * @param act the action provided by application
    971  * @return 'true' if action if 'continue' or 'finish' and need to be
    972  *         processed,
    973  *         'false' if action is 'suspend' or 'abort' and is already processed.
    974  */
    975 static MHD_FN_PAR_NONNULL_ (1) bool
    976 preprocess_dcc_action (struct MHD_Connection *restrict c,
    977                        const struct MHD_DynamicContentCreatorAction *act)
    978 {
    979   /**
    980    * The action created for the current request
    981    */
    982   struct MHD_DynamicContentCreatorAction *const a =
    983     &(c->rp.app_act);
    984 
    985   if (NULL != act)
    986   {
    987     if ((a != act) ||
    988         ! mhd_DCC_ACTION_IS_VALID (c->rp.app_act.act) ||
    989         ((MHD_SIZE_UNKNOWN != c->rp.response->cntn_size) &&
    990          (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)))
    991     {
    992       mhd_LOG_MSG (c->daemon, MHD_SC_ACTION_INVALID, \
    993                    "Provided Dynamic Content Creator action is not " \
    994                    "a correct action generated for the current request.");
    995       act = NULL;
    996     }
    997   }
    998   if (NULL == act)
    999     a->act = mhd_DCC_ACTION_ABORT;
   1000 
   1001   switch (a->act)
   1002   {
   1003   case mhd_DCC_ACTION_CONTINUE:
   1004     return true;
   1005   case mhd_DCC_ACTION_FINISH:
   1006     mhd_assert (MHD_SIZE_UNKNOWN == c->rp.response->cntn_size);
   1007     return true;
   1008   case mhd_DCC_ACTION_SUSPEND:
   1009     mhd_assert (0 && "Not implemented yet");
   1010     // TODO: Implement suspend;
   1011     mhd_STREAM_ABORT (c,
   1012                       mhd_CONN_CLOSE_INT_ERROR,
   1013                       "Suspending connection is not implemented yet");
   1014     return false;
   1015   case mhd_DCC_ACTION_ABORT:
   1016     mhd_STREAM_ABORT (c,
   1017                       mhd_CONN_CLOSE_APP_ABORTED,
   1018                       "Dynamic Content Creator requested abort " \
   1019                       "of the request");
   1020     return false;
   1021   case mhd_DCC_ACTION_NO_ACTION:
   1022   default:
   1023     break;
   1024   }
   1025   mhd_UNREACHABLE ();
   1026   mhd_STREAM_ABORT (c,
   1027                     mhd_CONN_CLOSE_INT_ERROR,
   1028                     "Impossible code path");
   1029   return false;
   1030 }
   1031 
   1032 
   1033 /**
   1034  * Read next portion of the response file to the buffer, based on information
   1035  * about amount of response content position.
   1036  * Handle file read errors, update response position.
   1037  * @param c the stream to use
   1038  * @param r the response to use
   1039  * @param buf_size the size of the @a buf buffer
   1040  * @param[out] buf the buffer to fill with the file data
   1041  * @param[out] size_filled the pointer to variable to get the size of the data
   1042  *                         actually put to the @a buffer
   1043  * @return 'true' if succeed (size_filled and response position are updated),
   1044  *         'false' if failed (the stream is closed)
   1045  */
   1046 static MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
   1047 MHD_FN_PAR_OUT_SIZE_ (4, 3) MHD_FN_PAR_OUT_ (5) bool
   1048 read_response_file (struct MHD_Connection *restrict c,
   1049                     struct MHD_Response *const restrict r,
   1050                     size_t buf_size,
   1051                     char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)],
   1052                     size_t *restrict size_filled)
   1053 {
   1054   mhd_assert (r == c->rp.response);
   1055   mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype);
   1056 
   1057   switch (mhd_read_file (r->cntn.file.fd,
   1058                          r->cntn.file.offset + c->rp.rsp_cntn_read_pos,
   1059                          buf_size,
   1060                          buf,
   1061                          size_filled))
   1062   {
   1063   case mhd_FILE_READ_OK:
   1064     break;
   1065   case mhd_FILE_READ_ERROR:
   1066     mhd_STREAM_ABORT (c, \
   1067                       mhd_CONN_CLOSE_FILE_READ_ERROR, \
   1068                       "Error reading file for file-backed response.");
   1069     return false;
   1070   case mhd_FILE_READ_OFFSET_TOO_LARGE:
   1071     mhd_STREAM_ABORT (c, \
   1072                       mhd_CONN_CLOSE_FILE_OFFSET_TOO_LARGE, \
   1073                       "The offset for file-backed response is too large " \
   1074                       "for this platform.");
   1075     return false;
   1076   case mhd_FILE_READ_EOF:
   1077     mhd_STREAM_ABORT (c, \
   1078                       mhd_CONN_CLOSE_FILE_TOO_SHORT, \
   1079                       "The file for file-backed response is smaller " \
   1080                       "than specified by application.");
   1081     return false;
   1082   default:
   1083     mhd_UNREACHABLE ();
   1084     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1085     return false;
   1086   }
   1087 
   1088   c->rp.rsp_cntn_read_pos += *size_filled;
   1089   return true;
   1090 }
   1091 
   1092 
   1093 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1094 mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c)
   1095 {
   1096   struct MHD_Response *const restrict r = c->rp.response;
   1097   uint64_t left_to_send;
   1098 
   1099   mhd_assert (c->rp.props.send_reply_body);
   1100   mhd_assert (c->rp.rsp_cntn_read_pos != r->cntn_size);
   1101 
   1102   mhd_stream_call_dcc_cleanup_if_needed (c);
   1103 
   1104   if (0 == r->cntn_size)
   1105   { /* 0-byte response is always ready */
   1106     c->stage = mhd_HTTP_STAGE_FULL_REPLY_SENT;
   1107     return true;
   1108   }
   1109 
   1110   mhd_assert (mhd_REPLY_CNTN_LOC_NOWHERE != c->rp.cntn_loc);
   1111   mhd_assert (mhd_RESPONSE_CONTENT_DATA_INVALID != r->cntn_dtype);
   1112 
   1113   if (MHD_SIZE_UNKNOWN == r->cntn_size)
   1114     left_to_send = MHD_SIZE_UNKNOWN;
   1115   else
   1116     left_to_send = r->cntn_size - c->rp.rsp_cntn_read_pos;
   1117 
   1118   mhd_assert (0 != left_to_send);
   1119 
   1120   if (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc)
   1121   {
   1122     (void) 0; /* Nothing to do, buffers are ready */
   1123   }
   1124   else if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc)
   1125   {
   1126     if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype)
   1127     {
   1128       const struct MHD_DynamicContentCreatorAction *act;
   1129       size_t size_to_fill =
   1130         c->write_buffer_size - c->write_buffer_append_offset;
   1131       size_t filled;
   1132 
   1133       if (size_to_fill > left_to_send)
   1134         size_to_fill = (size_t) left_to_send;
   1135 
   1136       mhd_assert (c->write_buffer_append_offset < c->write_buffer_size);
   1137       mhd_assert (NULL == c->rp.app_act_ctx.connection);
   1138 
   1139       c->rp.app_act_ctx.connection = c;
   1140       c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION;
   1141 
   1142       act =
   1143         r->cntn.dyn.cb (r->cntn.dyn.cls,
   1144                         &(c->rp.app_act_ctx),
   1145                         c->rp.rsp_cntn_read_pos,
   1146                         (void *)
   1147                         (c->write_buffer + c->write_buffer_append_offset),
   1148                         size_to_fill);
   1149       c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */
   1150       if (! preprocess_dcc_action (c, act))
   1151         return true;
   1152       if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)
   1153       {
   1154         mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size);
   1155         mhd_assert (c->rp.props.end_by_closing);
   1156 
   1157         c->stage = mhd_HTTP_STAGE_FULL_REPLY_SENT;
   1158 
   1159         return true;
   1160       }
   1161       mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act);
   1162       // TODO: implement iov sending
   1163 
   1164       filled = c->rp.app_act.data.cntnue.buf_data_size;
   1165       if (size_to_fill < filled)
   1166       {
   1167         mhd_STREAM_ABORT (c,
   1168                           mhd_CONN_CLOSE_APP_ERROR,
   1169                           "Closing connection (application returned more data "
   1170                           "than requested).");
   1171         return true;
   1172       }
   1173       c->rp.rsp_cntn_read_pos += filled;
   1174       c->write_buffer_append_offset += filled;
   1175     }
   1176     else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype)
   1177     {
   1178       size_t size_to_fill =
   1179         c->write_buffer_size - c->write_buffer_append_offset;
   1180       size_t filled;
   1181 
   1182       if (size_to_fill > left_to_send)
   1183         size_to_fill = (size_t) left_to_send;
   1184 
   1185       if (! read_response_file (c,
   1186                                 r,
   1187                                 size_to_fill,
   1188                                 c->write_buffer + c->write_buffer_append_offset,
   1189                                 &filled))
   1190         return true; /* Error, the stream is closed */
   1191 
   1192       c->write_buffer_append_offset += filled;
   1193     }
   1194     else
   1195     {
   1196       mhd_assert (0 && "Impossible value");
   1197       mhd_UNREACHABLE ();
   1198       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1199       c->rp.rsp_cntn_read_pos = r->cntn_size;
   1200     }
   1201   }
   1202   else if (mhd_REPLY_CNTN_LOC_IOV == c->rp.cntn_loc)
   1203   {
   1204     size_t copy_size;
   1205 
   1206     mhd_assert (NULL == c->rp.resp_iov.iov);
   1207     mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype);
   1208 
   1209     copy_size = r->cntn.iovec.cnt * sizeof(mhd_iovec);
   1210     c->rp.resp_iov.iov = (mhd_iovec *)
   1211                          mhd_stream_alloc_memory (c,
   1212                                                   copy_size);
   1213     if (NULL == c->rp.resp_iov.iov)
   1214     {
   1215       /* not enough memory */
   1216       mhd_STREAM_ABORT (c,
   1217                         mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1218                         "No memory in the pool for the response data.");
   1219       return false;
   1220     }
   1221     memcpy (c->rp.resp_iov.iov,
   1222             &(r->cntn.iovec.iov),
   1223             copy_size);
   1224     c->rp.resp_iov.cnt = r->cntn.iovec.cnt;
   1225     c->rp.resp_iov.sent = 0;
   1226   }
   1227 #if defined(mhd_USE_SENDFILE)
   1228   else if (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc)
   1229   {
   1230     (void) 0; /* Nothing to do, file should be read directly */
   1231   }
   1232 #endif /* mhd_USE_SENDFILE */
   1233   else
   1234   {
   1235     mhd_assert (0 && "Impossible value");
   1236     mhd_UNREACHABLE ();
   1237     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1238     c->rp.rsp_cntn_read_pos = r->cntn_size;
   1239   }
   1240 
   1241   c->stage = mhd_HTTP_STAGE_UNCHUNKED_BODY_READY;
   1242   return false;
   1243 }
   1244 
   1245 
   1246 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1247 mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c)
   1248 {
   1249   size_t filled;
   1250   struct MHD_Response *const restrict r = c->rp.response;
   1251   static const size_t max_chunk = 0xFFFFFF;
   1252   char chunk_hdr[6];            /* 6: max strlen of "FFFFFF" */
   1253   /* "FFFFFF" + "\r\n" */
   1254   static const size_t max_chunk_hdr_len = sizeof(chunk_hdr) + 2;
   1255   /* "FFFFFF" + "\r\n" + "\r\n" (chunk termination) */
   1256   static const size_t max_chunk_overhead = sizeof(chunk_hdr) + 2 + 2;
   1257   size_t chunk_hdr_len;
   1258   uint64_t left_to_send;
   1259   size_t size_to_fill;
   1260 
   1261   mhd_assert (0 == c->write_buffer_append_offset);
   1262   mhd_assert (0 == c->write_buffer_send_offset);
   1263 
   1264   mhd_stream_call_dcc_cleanup_if_needed (c);
   1265 
   1266   /* The buffer must be reasonably large enough */
   1267   if (32 > c->write_buffer_size)
   1268   {
   1269     mhd_STREAM_ABORT (c,
   1270                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1271                       "No memory in the pool for the reply chunked content.");
   1272     return true;
   1273   }
   1274   mhd_assert (max_chunk_overhead < \
   1275               (c->write_buffer_size));
   1276 
   1277   if (MHD_SIZE_UNKNOWN == r->cntn_size)
   1278     left_to_send = MHD_SIZE_UNKNOWN;
   1279   else
   1280     left_to_send = r->cntn_size - c->rp.rsp_cntn_read_pos;
   1281 
   1282   mhd_assert (0 != left_to_send);
   1283   if (0 != left_to_send)
   1284   {
   1285     size_to_fill =
   1286       c->write_buffer_size - max_chunk_overhead;
   1287     /* Limit size for the callback to the max usable size */
   1288     if (max_chunk < size_to_fill)
   1289       size_to_fill = max_chunk;
   1290     if (left_to_send < size_to_fill)
   1291       size_to_fill = (size_t) left_to_send;
   1292   }
   1293   else
   1294     size_to_fill = 0;
   1295 
   1296   if ((0 == left_to_send) &&
   1297       (mhd_RESPONSE_CONTENT_DATA_CALLBACK != r->cntn_dtype))
   1298   {
   1299     c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_SENT;
   1300     return true;
   1301   }
   1302   else if (mhd_RESPONSE_CONTENT_DATA_BUFFER == r->cntn_dtype)
   1303   {
   1304     mhd_assert (size_to_fill <= \
   1305                 r->cntn_size - (size_t) c->rp.rsp_cntn_read_pos);
   1306     memcpy (c->write_buffer + max_chunk_hdr_len,
   1307             r->cntn.buf + (size_t) c->rp.rsp_cntn_read_pos,
   1308             size_to_fill);
   1309     filled = size_to_fill;
   1310   }
   1311   else if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype)
   1312   {
   1313     const struct MHD_DynamicContentCreatorAction *act;
   1314 
   1315     mhd_assert (NULL == c->rp.app_act_ctx.connection);
   1316 
   1317     c->rp.app_act_ctx.connection = c;
   1318     c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION;
   1319 
   1320     act =
   1321       r->cntn.dyn.cb (r->cntn.dyn.cls,
   1322                       &(c->rp.app_act_ctx),
   1323                       c->rp.rsp_cntn_read_pos,
   1324                       (void *)
   1325                       (c->write_buffer + max_chunk_hdr_len),
   1326                       size_to_fill);
   1327     c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */
   1328     if (! preprocess_dcc_action (c, act))
   1329       return true;
   1330     if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)
   1331     {
   1332       mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size);
   1333       c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_SENT;
   1334 
   1335       return true;
   1336     }
   1337     mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act);
   1338     // TODO: implement iov sending
   1339 
   1340     filled = c->rp.app_act.data.cntnue.buf_data_size;
   1341     if (size_to_fill < filled)
   1342     {
   1343       mhd_STREAM_ABORT (c,
   1344                         mhd_CONN_CLOSE_APP_ERROR,
   1345                         "Closing connection (application returned more data "
   1346                         "than requested).");
   1347       return true;
   1348     }
   1349     c->rp.rsp_cntn_read_pos += filled;
   1350     c->write_buffer_append_offset += filled;
   1351   }
   1352   else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype)
   1353   {
   1354     if (! read_response_file (c,
   1355                               r,
   1356                               size_to_fill,
   1357                               c->write_buffer + max_chunk_hdr_len,
   1358                               &filled))
   1359       return true; /* Error, the stream is closed */
   1360 
   1361     c->write_buffer_append_offset += filled;
   1362   }
   1363   else
   1364   {
   1365     mhd_assert (0 && "Not implemented yet");
   1366     filled = 0;
   1367   }
   1368 
   1369   chunk_hdr_len = mhd_uint32_to_strx ((uint_fast32_t) filled,
   1370                                       chunk_hdr,
   1371                                       sizeof(chunk_hdr));
   1372   mhd_assert (chunk_hdr_len != 0);
   1373   mhd_assert (chunk_hdr_len < sizeof(chunk_hdr));
   1374   c->write_buffer_send_offset = max_chunk_hdr_len - (chunk_hdr_len + 2);
   1375   memcpy (c->write_buffer + c->write_buffer_send_offset,
   1376           chunk_hdr,
   1377           chunk_hdr_len);
   1378   c->write_buffer[max_chunk_hdr_len - 2] = '\r';
   1379   c->write_buffer[max_chunk_hdr_len - 1] = '\n';
   1380   c->write_buffer[max_chunk_hdr_len + filled] = '\r';
   1381   c->write_buffer[max_chunk_hdr_len + filled + 1] = '\n';
   1382   c->write_buffer_append_offset = max_chunk_hdr_len + filled + 2;
   1383   if (0 != filled)
   1384     c->rp.rsp_cntn_read_pos += filled;
   1385   else
   1386     c->rp.rsp_cntn_read_pos = r->cntn_size;
   1387 
   1388   c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_READY;
   1389 
   1390   return false;
   1391 }
   1392 
   1393 
   1394 /**
   1395  * Allocate the connection's write buffer (if necessary) and fill it
   1396  * with response footers.
   1397  * Inner version.
   1398  *
   1399  * @param c the connection
   1400  * @return 'true' if footers formed successfully,
   1401  *         'false' if not enough buffer
   1402  */
   1403 static MHD_FN_PAR_NONNULL_ALL_ bool
   1404 prep_chunked_footer_inn (struct MHD_Connection *restrict c)
   1405 {
   1406   char *buf;           /**< the buffer to write footers to */
   1407   size_t buf_size;     /**< the size of the @a buf */
   1408   size_t used_size;    /**< the used size of the @a buf */
   1409   // struct MHD_HTTP_Res_Header *pos;
   1410 
   1411   mhd_assert (c->rp.props.chunked);
   1412   mhd_assert (mhd_HTTP_STAGE_CHUNKED_BODY_SENT == c->stage);
   1413   mhd_assert (NULL != c->rp.response);
   1414 
   1415   buf_size = mhd_stream_maximize_write_buffer (c);
   1416   /* '5' is the minimal size of chunked footer ("0\r\n\r\n") */
   1417   if (buf_size < 5)
   1418     return false;
   1419   mhd_assert (NULL != c->write_buffer);
   1420   buf = c->write_buffer + c->write_buffer_append_offset;
   1421   mhd_assert (NULL != buf);
   1422   used_size = 0;
   1423   buf[used_size++] = '0';
   1424   buf[used_size++] = '\r';
   1425   buf[used_size++] = '\n';
   1426 
   1427 #if 0 // TODO: use dynamic/connection's footers
   1428   for (pos = c->rp.response->first_header; NULL != pos; pos = pos->next)
   1429   {
   1430     if (MHD_FOOTER_KIND == pos->kind)
   1431     {
   1432       size_t new_used_size; /* resulting size with this header */
   1433       /* '4' is colon, space, linefeeds */
   1434       new_used_size = used_size + pos->header_size + pos->value_size + 4;
   1435       if (new_used_size > buf_size)
   1436         return MHD_NO;
   1437       memcpy (buf + used_size, pos->header, pos->header_size);
   1438       used_size += pos->header_size;
   1439       buf[used_size++] = ':';
   1440       buf[used_size++] = ' ';
   1441       memcpy (buf + used_size, pos->value, pos->value_size);
   1442       used_size += pos->value_size;
   1443       buf[used_size++] = '\r';
   1444       buf[used_size++] = '\n';
   1445       mhd_assert (used_size == new_used_size);
   1446     }
   1447   }
   1448 #endif
   1449 
   1450   if (used_size + 2 > buf_size)
   1451     return false;
   1452   buf[used_size++] = '\r';
   1453   buf[used_size++] = '\n';
   1454 
   1455   c->write_buffer_append_offset += used_size;
   1456   mhd_assert (c->write_buffer_append_offset <= c->write_buffer_size);
   1457 
   1458   return true;
   1459 }
   1460 
   1461 
   1462 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1463 mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c)
   1464 {
   1465   if (! prep_chunked_footer_inn (c))
   1466   {
   1467     mhd_STREAM_ABORT (c,
   1468                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1469                       "No memory in the pool for the reply chunked footer.");
   1470     return true;
   1471   }
   1472   c->stage = mhd_HTTP_STAGE_FOOTERS_SENDING;
   1473   return false;
   1474 }