libmicrohttpd2

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

stream_process_reply.c (46286B)


      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     pos += r->special_resp.spec_hdr_len;
    811     buf[pos++] = '\r';
    812     buf[pos++] = '\n';
    813   }
    814 
    815   /* Main automatic headers */
    816 
    817   /* The "Date:" header */
    818   if ( (! r->cfg.has_hdr_date) &&
    819        (! c->daemon->req_cfg.suppress_date) )
    820   {
    821     /* Additional byte for unused zero-termination */
    822     if (buf_size < pos + 38)
    823       return false;
    824     if (get_date_header (buf + pos))
    825       pos += 37;
    826   }
    827   /* The "Connection:" header */
    828   mhd_assert (! use_conn_close || ! use_conn_k_alive);
    829   mhd_assert (! use_conn_k_alive || ! use_conn_close);
    830   if (! r->cfg.has_hdr_conn)
    831   {
    832     if (use_conn_close)
    833     {
    834       if (! buffer_append_s (buf, &pos, buf_size,
    835                              MHD_HTTP_HEADER_CONNECTION ": close\r\n"))
    836         return false;
    837     }
    838     else if (use_conn_k_alive)
    839     {
    840       if (! buffer_append_s (buf, &pos, buf_size,
    841                              MHD_HTTP_HEADER_CONNECTION ": Keep-Alive\r\n"))
    842         return false;
    843     }
    844     use_conn_close = false;
    845     use_conn_k_alive = false;
    846   }
    847 
    848   /* Special headers */
    849 #ifdef MHD_SUPPORT_AUTH_DIGEST
    850   if (mhd_RESP_HAS_AUTH_DIGEST (r))
    851   {
    852     char noncestr[mhd_AUTH_DIGEST_NONCE_LEN];
    853     const struct mhd_RespAuthDigestHeader *dg_hdr;
    854     if (! mhd_auth_digest_get_new_nonce (c,
    855                                          noncestr))
    856     {
    857       mhd_STREAM_ABORT (c,
    858                         mhd_CONN_CLOSE_NONCE_ERROR,
    859                         "Failed to generate a new nonce for Digest Auth.");
    860       return false;
    861     }
    862     for (dg_hdr = mhd_DLINKEDL_GET_FIRST (r, auth_d_hdrs);
    863          NULL != dg_hdr;
    864          dg_hdr = mhd_DLINKEDL_GET_NEXT (dg_hdr, auth_d_hdrs))
    865     {
    866       size_t nonce_pos;
    867       nonce_pos = pos + dg_hdr->nonce_pos;
    868       if (! buffer_append_mstr (buf, &pos, buf_size, \
    869                                 &(dg_hdr->hdr)))
    870         return false;
    871       memcpy (buf + nonce_pos,
    872               noncestr,
    873               sizeof(noncestr));
    874     }
    875   }
    876 #endif /* MHD_SUPPORT_AUTH_DIGEST */
    877 
    878   /* User-defined headers */
    879 
    880   if (! add_user_headers (buf, &pos, buf_size, r,
    881                           ! c->rp.props.use_reply_body_headers,
    882                           use_conn_close,
    883                           use_conn_k_alive))
    884     return false;
    885 
    886   /* Other automatic headers */
    887 
    888   if (c->rp.props.use_reply_body_headers)
    889   {
    890     /* Body-specific headers */
    891 
    892     if (c->rp.props.chunked)
    893     { /* Chunked encoding is used */
    894       mhd_assert (! c->rp.props.end_by_closing);
    895       if (! buffer_append_s (buf, &pos, buf_size,
    896                              MHD_HTTP_HEADER_TRANSFER_ENCODING ": " \
    897                              "chunked\r\n"))
    898         return false;
    899     }
    900     else /* Chunked encoding is not used */
    901     {
    902       if ((MHD_SIZE_UNKNOWN != r->cntn_size) &&
    903           (! c->rp.props.end_by_closing) &&
    904           (! r->cfg.chunked) &&
    905           (! r->cfg.head_only))
    906       { /* The size is known and can be indicated by the header */
    907         if (! r->cfg.cnt_len_by_app)
    908         { /* The response does not have app-defined "Content-Length" header */
    909           if (! buffer_append_s (buf, &pos, buf_size,
    910                                  MHD_HTTP_HEADER_CONTENT_LENGTH ": "))
    911             return false;
    912           el_size = mhd_uint64_to_str (r->cntn_size,
    913                                        buf + pos,
    914                                        buf_size - pos);
    915           if (0 == el_size)
    916             return false;
    917           pos += el_size;
    918 
    919           if (buf_size < pos + 2)
    920             return false;
    921           buf[pos++] = '\r';
    922           buf[pos++] = '\n';
    923         }
    924       }
    925       else
    926       {
    927         mhd_assert ((! c->rp.props.send_reply_body) || \
    928                     (mhd_CONN_MUST_CLOSE == c->conn_reuse));
    929         (void) 0;
    930       }
    931     }
    932   }
    933 
    934   /* * Header termination * */
    935   if (buf_size < pos + 2)
    936     return false;
    937   buf[pos++] = '\r';
    938   buf[pos++] = '\n';
    939 
    940   c->write_buffer_append_offset = pos;
    941   return true;
    942 }
    943 
    944 
    945 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
    946 mhd_stream_build_header_response (struct MHD_Connection *restrict c)
    947 {
    948   if (! build_header_response_inn (c))
    949   {
    950 #ifdef MHD_SUPPORT_AUTH_DIGEST
    951     if (mhd_HTTP_STAGE_PRE_CLOSING <= c->stage)
    952       return false; /* Already started closing */
    953 #else  /* ! MHD_SUPPORT_AUTH_DIGEST */
    954     mhd_assert (mhd_HTTP_STAGE_START_REPLY == c->stage);
    955 #endif /* ! MHD_SUPPORT_AUTH_DIGEST */
    956     mhd_STREAM_ABORT (c,
    957                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
    958                       "No memory in the pool for the reply headers.");
    959     return false;
    960   }
    961   c->stage = mhd_HTTP_STAGE_HEADERS_SENDING;
    962   return true;
    963 }
    964 
    965 
    966 /**
    967  * Pre-process DCC action provided by application.
    968  * 'abort' and 'suspend' actions are fully processed,
    969  * 'continue' and 'finish' actions needs to be processed by the caller.
    970  * @param c the stream to use
    971  * @param act the action provided by application
    972  * @return 'true' if action if 'continue' or 'finish' and need to be
    973  *         processed,
    974  *         'false' if action is 'suspend' or 'abort' and is already processed.
    975  */
    976 static MHD_FN_PAR_NONNULL_ (1) bool
    977 preprocess_dcc_action (struct MHD_Connection *restrict c,
    978                        const struct MHD_DynamicContentCreatorAction *act)
    979 {
    980   /**
    981    * The action created for the current request
    982    */
    983   struct MHD_DynamicContentCreatorAction *const a =
    984     &(c->rp.app_act);
    985 
    986   if (NULL != act)
    987   {
    988     if ((a != act) ||
    989         ! mhd_DCC_ACTION_IS_VALID (c->rp.app_act.act) ||
    990         ((MHD_SIZE_UNKNOWN != c->rp.response->cntn_size) &&
    991          (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)))
    992     {
    993       mhd_LOG_MSG (c->daemon, MHD_SC_ACTION_INVALID, \
    994                    "Provided Dynamic Content Creator action is not " \
    995                    "a correct action generated for the current request.");
    996       act = NULL;
    997     }
    998   }
    999   if (NULL == act)
   1000     a->act = mhd_DCC_ACTION_ABORT;
   1001 
   1002   switch (a->act)
   1003   {
   1004   case mhd_DCC_ACTION_CONTINUE:
   1005     return true;
   1006   case mhd_DCC_ACTION_FINISH:
   1007     mhd_assert (MHD_SIZE_UNKNOWN == c->rp.response->cntn_size);
   1008     return true;
   1009   case mhd_DCC_ACTION_SUSPEND:
   1010     mhd_assert (0 && "Not implemented yet");
   1011     // TODO: Implement suspend;
   1012     mhd_STREAM_ABORT (c,
   1013                       mhd_CONN_CLOSE_INT_ERROR,
   1014                       "Suspending connection is not implemented yet");
   1015     return false;
   1016   case mhd_DCC_ACTION_ABORT:
   1017     mhd_STREAM_ABORT (c,
   1018                       mhd_CONN_CLOSE_APP_ABORTED,
   1019                       "Dynamic Content Creator requested abort " \
   1020                       "of the request");
   1021     return false;
   1022   case mhd_DCC_ACTION_NO_ACTION:
   1023   default:
   1024     break;
   1025   }
   1026   mhd_UNREACHABLE ();
   1027   mhd_STREAM_ABORT (c,
   1028                     mhd_CONN_CLOSE_INT_ERROR,
   1029                     "Impossible code path");
   1030   return false;
   1031 }
   1032 
   1033 
   1034 /**
   1035  * Read next portion of the response file to the buffer, based on information
   1036  * about amount of response content position.
   1037  * Handle file read errors, does NOT update response position (caller
   1038  * should do so on success).
   1039  * @param c the stream to use
   1040  * @param r the response to use
   1041  * @param buf_size the size of the @a buf buffer
   1042  * @param[out] buf the buffer to fill with the file data
   1043  * @param[out] size_filled the pointer to variable to get the size of the data
   1044  *                         actually put to the @a buffer
   1045  * @return 'true' if succeed (size_filled and response position are updated),
   1046  *         'false' if failed (the stream is closed)
   1047  */
   1048 static MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_
   1049 MHD_FN_PAR_OUT_SIZE_ (4, 3) MHD_FN_PAR_OUT_ (5) bool
   1050 read_response_file (struct MHD_Connection *restrict c,
   1051                     struct MHD_Response *const restrict r,
   1052                     size_t buf_size,
   1053                     char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)],
   1054                     size_t *restrict size_filled)
   1055 {
   1056   mhd_assert (r == c->rp.response);
   1057   mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype);
   1058 
   1059   switch (mhd_read_file (r->cntn.file.fd,
   1060                          r->cntn.file.offset + c->rp.rsp_cntn_read_pos,
   1061                          buf_size,
   1062                          buf,
   1063                          size_filled))
   1064   {
   1065   case mhd_FILE_READ_OK:
   1066     break;
   1067   case mhd_FILE_READ_ERROR:
   1068     mhd_STREAM_ABORT (c, \
   1069                       mhd_CONN_CLOSE_FILE_READ_ERROR, \
   1070                       "Error reading file for file-backed response.");
   1071     return false;
   1072   case mhd_FILE_READ_OFFSET_TOO_LARGE:
   1073     mhd_STREAM_ABORT (c, \
   1074                       mhd_CONN_CLOSE_FILE_OFFSET_TOO_LARGE, \
   1075                       "The offset for file-backed response is too large " \
   1076                       "for this platform.");
   1077     return false;
   1078   case mhd_FILE_READ_EOF:
   1079     mhd_STREAM_ABORT (c, \
   1080                       mhd_CONN_CLOSE_FILE_TOO_SHORT, \
   1081                       "The file for file-backed response is smaller " \
   1082                       "than specified by application.");
   1083     return false;
   1084   default:
   1085     mhd_UNREACHABLE ();
   1086     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1087     return false;
   1088   }
   1089 
   1090   return true;
   1091 }
   1092 
   1093 
   1094 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1095 mhd_stream_prep_unchunked_body (struct MHD_Connection *restrict c)
   1096 {
   1097   struct MHD_Response *const restrict r = c->rp.response;
   1098   uint64_t left_to_send;
   1099 
   1100   mhd_assert (c->rp.props.send_reply_body);
   1101   mhd_assert (c->rp.rsp_cntn_read_pos != r->cntn_size);
   1102 
   1103   mhd_stream_call_dcc_cleanup_if_needed (c);
   1104 
   1105   if (0 == r->cntn_size)
   1106   { /* 0-byte response is always ready */
   1107     c->stage = mhd_HTTP_STAGE_FULL_REPLY_SENT;
   1108     return true;
   1109   }
   1110 
   1111   mhd_assert (mhd_REPLY_CNTN_LOC_NOWHERE != c->rp.cntn_loc);
   1112   mhd_assert (mhd_RESPONSE_CONTENT_DATA_INVALID != r->cntn_dtype);
   1113 
   1114   if (MHD_SIZE_UNKNOWN == r->cntn_size)
   1115     left_to_send = MHD_SIZE_UNKNOWN;
   1116   else
   1117     left_to_send = r->cntn_size - c->rp.rsp_cntn_read_pos;
   1118 
   1119   mhd_assert (0 != left_to_send);
   1120 
   1121   if (mhd_REPLY_CNTN_LOC_RESP_BUF == c->rp.cntn_loc)
   1122   {
   1123     (void) 0; /* Nothing to do, buffers are ready */
   1124   }
   1125   else if (mhd_REPLY_CNTN_LOC_CONN_BUF == c->rp.cntn_loc)
   1126   {
   1127     if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype)
   1128     {
   1129       const struct MHD_DynamicContentCreatorAction *act;
   1130       size_t size_to_fill =
   1131         c->write_buffer_size - c->write_buffer_append_offset;
   1132       size_t filled;
   1133 
   1134       if (size_to_fill > left_to_send)
   1135         size_to_fill = (size_t) left_to_send;
   1136 
   1137       mhd_assert (c->write_buffer_append_offset < c->write_buffer_size);
   1138       mhd_assert (NULL == c->rp.app_act_ctx.connection);
   1139 
   1140       c->rp.app_act_ctx.connection = c;
   1141       c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION;
   1142 
   1143       act =
   1144         r->cntn.dyn.cb (r->cntn.dyn.cls,
   1145                         &(c->rp.app_act_ctx),
   1146                         c->rp.rsp_cntn_read_pos,
   1147                         (void *)
   1148                         (c->write_buffer + c->write_buffer_append_offset),
   1149                         size_to_fill);
   1150       c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */
   1151       if (! preprocess_dcc_action (c, act))
   1152         return true;
   1153       if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)
   1154       {
   1155         mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size);
   1156         mhd_assert (c->rp.props.end_by_closing);
   1157 
   1158         c->stage = mhd_HTTP_STAGE_FULL_REPLY_SENT;
   1159 
   1160         return true;
   1161       }
   1162       mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act);
   1163       // TODO: implement iov sending
   1164 
   1165       filled = c->rp.app_act.data.cntnue.buf_data_size;
   1166       if (size_to_fill < filled)
   1167       {
   1168         mhd_STREAM_ABORT (c,
   1169                           mhd_CONN_CLOSE_APP_ERROR,
   1170                           "Closing connection (application returned more data "
   1171                           "than requested).");
   1172         return true;
   1173       }
   1174       c->rp.rsp_cntn_read_pos += filled;
   1175       c->write_buffer_append_offset += filled;
   1176     }
   1177     else if (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype)
   1178     {
   1179       size_t size_to_fill =
   1180         c->write_buffer_size - c->write_buffer_append_offset;
   1181       size_t filled;
   1182 
   1183       if (size_to_fill > left_to_send)
   1184         size_to_fill = (size_t) left_to_send;
   1185 
   1186       if (! read_response_file (c,
   1187                                 r,
   1188                                 size_to_fill,
   1189                                 c->write_buffer + c->write_buffer_append_offset,
   1190                                 &filled))
   1191         return true; /* Error, the stream is closed */
   1192 
   1193       c->rp.rsp_cntn_read_pos += filled;
   1194       c->write_buffer_append_offset += filled;
   1195     }
   1196     else
   1197     {
   1198       mhd_assert (0 && "Impossible value");
   1199       mhd_UNREACHABLE ();
   1200       c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1201       c->rp.rsp_cntn_read_pos = r->cntn_size;
   1202     }
   1203   }
   1204   else if (mhd_REPLY_CNTN_LOC_IOV == c->rp.cntn_loc)
   1205   {
   1206     size_t copy_size;
   1207 
   1208     mhd_assert (NULL == c->rp.resp_iov.iov);
   1209     mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype);
   1210 
   1211     copy_size = r->cntn.iovec.cnt * sizeof(mhd_iovec);
   1212     c->rp.resp_iov.iov = (mhd_iovec *)
   1213                          mhd_stream_alloc_memory (c,
   1214                                                   copy_size);
   1215     if (NULL == c->rp.resp_iov.iov)
   1216     {
   1217       /* not enough memory */
   1218       mhd_STREAM_ABORT (c,
   1219                         mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1220                         "No memory in the pool for the response data.");
   1221       return false;
   1222     }
   1223     memcpy (c->rp.resp_iov.iov,
   1224             &(r->cntn.iovec.iov),
   1225             copy_size);
   1226     c->rp.resp_iov.cnt = r->cntn.iovec.cnt;
   1227     c->rp.resp_iov.sent = 0;
   1228   }
   1229 #if defined(mhd_USE_SENDFILE)
   1230   else if (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc)
   1231   {
   1232     (void) 0; /* Nothing to do, file should be read directly */
   1233   }
   1234 #endif /* mhd_USE_SENDFILE */
   1235   else
   1236   {
   1237     mhd_assert (0 && "Impossible value");
   1238     mhd_UNREACHABLE ();
   1239     c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_NOWHERE;
   1240     c->rp.rsp_cntn_read_pos = r->cntn_size;
   1241   }
   1242 
   1243   c->stage = mhd_HTTP_STAGE_UNCHUNKED_BODY_READY;
   1244   return false;
   1245 }
   1246 
   1247 
   1248 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1249 mhd_stream_prep_chunked_body (struct MHD_Connection *restrict c)
   1250 {
   1251   size_t filled;
   1252   struct MHD_Response *const restrict r = c->rp.response;
   1253   static const size_t max_chunk = 0xFFFFFF;
   1254   char chunk_hdr[6];            /* 6: max strlen of "FFFFFF" */
   1255   /* "FFFFFF" + "\r\n" */
   1256   static const size_t max_chunk_hdr_len = sizeof(chunk_hdr) + 2;
   1257   /* "FFFFFF" + "\r\n" + "\r\n" (chunk termination) */
   1258   static const size_t max_chunk_overhead = sizeof(chunk_hdr) + 2 + 2;
   1259   size_t chunk_hdr_len;
   1260   uint64_t left_to_send;
   1261   size_t size_to_fill;
   1262 
   1263   mhd_assert (0 == c->write_buffer_append_offset);
   1264   mhd_assert (0 == c->write_buffer_send_offset);
   1265 
   1266   mhd_stream_call_dcc_cleanup_if_needed (c);
   1267 
   1268   /* The buffer must be reasonably large enough */
   1269   if (32 > c->write_buffer_size)
   1270   {
   1271     mhd_STREAM_ABORT (c,
   1272                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1273                       "No memory in the pool for the reply chunked content.");
   1274     return true;
   1275   }
   1276   mhd_assert (max_chunk_overhead < \
   1277               (c->write_buffer_size));
   1278 
   1279   if (MHD_SIZE_UNKNOWN == r->cntn_size)
   1280     left_to_send = MHD_SIZE_UNKNOWN;
   1281   else
   1282     left_to_send = r->cntn_size - c->rp.rsp_cntn_read_pos;
   1283 
   1284   mhd_assert (0 != left_to_send);
   1285   if (0 != left_to_send)
   1286   {
   1287     size_to_fill =
   1288       c->write_buffer_size - max_chunk_overhead;
   1289     /* Limit size for the callback to the max usable size */
   1290     if (max_chunk < size_to_fill)
   1291       size_to_fill = max_chunk;
   1292     if (left_to_send < size_to_fill)
   1293       size_to_fill = (size_t) left_to_send;
   1294   }
   1295   else
   1296     size_to_fill = 0;
   1297 
   1298   if ((0 == left_to_send) &&
   1299       (mhd_RESPONSE_CONTENT_DATA_CALLBACK != r->cntn_dtype))
   1300   {
   1301     c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_SENT;
   1302     return true;
   1303   }
   1304   else if (mhd_RESPONSE_CONTENT_DATA_BUFFER == r->cntn_dtype)
   1305   {
   1306     mhd_assert (size_to_fill <= \
   1307                 r->cntn_size - (size_t) c->rp.rsp_cntn_read_pos);
   1308     memcpy (c->write_buffer + max_chunk_hdr_len,
   1309             r->cntn.buf + (size_t) c->rp.rsp_cntn_read_pos,
   1310             size_to_fill);
   1311     filled = size_to_fill;
   1312   }
   1313   else if (mhd_RESPONSE_CONTENT_DATA_CALLBACK == r->cntn_dtype)
   1314   {
   1315     const struct MHD_DynamicContentCreatorAction *act;
   1316 
   1317     mhd_assert (NULL == c->rp.app_act_ctx.connection);
   1318 
   1319     c->rp.app_act_ctx.connection = c;
   1320     c->rp.app_act.act = mhd_DCC_ACTION_NO_ACTION;
   1321 
   1322     act =
   1323       r->cntn.dyn.cb (r->cntn.dyn.cls,
   1324                       &(c->rp.app_act_ctx),
   1325                       c->rp.rsp_cntn_read_pos,
   1326                       (void *)
   1327                       (c->write_buffer + max_chunk_hdr_len),
   1328                       size_to_fill);
   1329     c->rp.app_act_ctx.connection = NULL; /* Block any attempt to create a new action */
   1330     if (! preprocess_dcc_action (c, act))
   1331       return true;
   1332     if (mhd_DCC_ACTION_FINISH == c->rp.app_act.act)
   1333     {
   1334       mhd_assert (MHD_SIZE_UNKNOWN == r->cntn_size);
   1335       c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_SENT;
   1336 
   1337       return true;
   1338     }
   1339     mhd_assert (mhd_DCC_ACTION_CONTINUE == c->rp.app_act.act);
   1340     // TODO: implement iov sending
   1341 
   1342     filled = c->rp.app_act.data.cntnue.buf_data_size;
   1343     if (size_to_fill < filled)
   1344     {
   1345       mhd_STREAM_ABORT (c,
   1346                         mhd_CONN_CLOSE_APP_ERROR,
   1347                         "Closing connection (application returned more data "
   1348                         "than requested).");
   1349       return true;
   1350     }
   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   else
   1362   {
   1363     mhd_assert (0 && "Not implemented yet");
   1364     filled = 0;
   1365   }
   1366 
   1367   chunk_hdr_len = mhd_uint32_to_strx ((uint_fast32_t) filled,
   1368                                       chunk_hdr,
   1369                                       sizeof(chunk_hdr));
   1370   mhd_assert (chunk_hdr_len != 0);
   1371   mhd_assert (chunk_hdr_len < sizeof(chunk_hdr));
   1372   c->write_buffer_send_offset = max_chunk_hdr_len - (chunk_hdr_len + 2);
   1373   memcpy (c->write_buffer + c->write_buffer_send_offset,
   1374           chunk_hdr,
   1375           chunk_hdr_len);
   1376   c->write_buffer[max_chunk_hdr_len - 2] = '\r';
   1377   c->write_buffer[max_chunk_hdr_len - 1] = '\n';
   1378   c->write_buffer[max_chunk_hdr_len + filled] = '\r';
   1379   c->write_buffer[max_chunk_hdr_len + filled + 1] = '\n';
   1380   c->write_buffer_append_offset = max_chunk_hdr_len + filled + 2;
   1381   if (0 != filled)
   1382     c->rp.rsp_cntn_read_pos += filled;
   1383   else
   1384     c->rp.rsp_cntn_read_pos = r->cntn_size;
   1385 
   1386   c->stage = mhd_HTTP_STAGE_CHUNKED_BODY_READY;
   1387 
   1388   return false;
   1389 }
   1390 
   1391 
   1392 /**
   1393  * Allocate the connection's write buffer (if necessary) and fill it
   1394  * with response footers.
   1395  * Inner version.
   1396  *
   1397  * @param c the connection
   1398  * @return 'true' if footers formed successfully,
   1399  *         'false' if not enough buffer
   1400  */
   1401 static MHD_FN_PAR_NONNULL_ALL_ bool
   1402 prep_chunked_footer_inn (struct MHD_Connection *restrict c)
   1403 {
   1404   char *buf;           /**< the buffer to write footers to */
   1405   size_t buf_size;     /**< the size of the @a buf */
   1406   size_t used_size;    /**< the used size of the @a buf */
   1407   // struct MHD_HTTP_Res_Header *pos;
   1408 
   1409   mhd_assert (c->rp.props.chunked);
   1410   mhd_assert (mhd_HTTP_STAGE_CHUNKED_BODY_SENT == c->stage);
   1411   mhd_assert (NULL != c->rp.response);
   1412 
   1413   buf_size = mhd_stream_maximize_write_buffer (c);
   1414   /* '5' is the minimal size of chunked footer ("0\r\n\r\n") */
   1415   if (buf_size < 5)
   1416     return false;
   1417   mhd_assert (NULL != c->write_buffer);
   1418   buf = c->write_buffer + c->write_buffer_append_offset;
   1419   mhd_assert (NULL != buf);
   1420   used_size = 0;
   1421   buf[used_size++] = '0';
   1422   buf[used_size++] = '\r';
   1423   buf[used_size++] = '\n';
   1424 
   1425 #if 0 // TODO: use dynamic/connection's footers
   1426   for (pos = c->rp.response->first_header; NULL != pos; pos = pos->next)
   1427   {
   1428     if (MHD_FOOTER_KIND == pos->kind)
   1429     {
   1430       size_t new_used_size; /* resulting size with this header */
   1431       /* '4' is colon, space, linefeeds */
   1432       new_used_size = used_size + pos->header_size + pos->value_size + 4;
   1433       if (new_used_size > buf_size)
   1434         return MHD_NO;
   1435       memcpy (buf + used_size, pos->header, pos->header_size);
   1436       used_size += pos->header_size;
   1437       buf[used_size++] = ':';
   1438       buf[used_size++] = ' ';
   1439       memcpy (buf + used_size, pos->value, pos->value_size);
   1440       used_size += pos->value_size;
   1441       buf[used_size++] = '\r';
   1442       buf[used_size++] = '\n';
   1443       mhd_assert (used_size == new_used_size);
   1444     }
   1445   }
   1446 #endif
   1447 
   1448   if (used_size + 2 > buf_size)
   1449     return false;
   1450   buf[used_size++] = '\r';
   1451   buf[used_size++] = '\n';
   1452 
   1453   c->write_buffer_append_offset += used_size;
   1454   mhd_assert (c->write_buffer_append_offset <= c->write_buffer_size);
   1455 
   1456   return true;
   1457 }
   1458 
   1459 
   1460 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
   1461 mhd_stream_prep_chunked_footer (struct MHD_Connection *restrict c)
   1462 {
   1463   if (! prep_chunked_footer_inn (c))
   1464   {
   1465     mhd_STREAM_ABORT (c,
   1466                       mhd_CONN_CLOSE_NO_POOL_MEM_FOR_REPLY,
   1467                       "No memory in the pool for the reply chunked footer.");
   1468     return true;
   1469   }
   1470   c->stage = mhd_HTTP_STAGE_FOOTERS_SENDING;
   1471   return false;
   1472 }