libmicrohttpd2

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

h2_reply_funcs.c (19511B)


      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) 2025 Evgeny Grin (Karlson2k)
      5 
      6   GNU libmicrohttpd is free software; you can redistribute it and/or
      7   modify it under the terms of the GNU Lesser General Public
      8   License as published by the Free Software Foundation; either
      9   version 2.1 of the License, or (at your option) any later version.
     10 
     11   GNU libmicrohttpd is distributed in the hope that it will be useful,
     12   but WITHOUT ANY WARRANTY; without even the implied warranty of
     13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14   Lesser General Public License for more details.
     15 
     16   Alternatively, you can redistribute GNU libmicrohttpd and/or
     17   modify it under the terms of the GNU General Public License as
     18   published by the Free Software Foundation; either version 2 of
     19   the License, or (at your option) any later version, together
     20   with the eCos exception, as follows:
     21 
     22     As a special exception, if other files instantiate templates or
     23     use macros or inline functions from this file, or you compile this
     24     file and link it with other works to produce a work based on this
     25     file, this file does not by itself cause the resulting work to be
     26     covered by the GNU General Public License. However the source code
     27     for this file must still be made available in accordance with
     28     section (3) of the GNU General Public License v2.
     29 
     30     This exception does not invalidate any other reasons why a work
     31     based on this file might be covered by the GNU General Public
     32     License.
     33 
     34   You should have received copies of the GNU Lesser General Public
     35   License and the GNU General Public License along with this library;
     36   if not, see <https://www.gnu.org/licenses/>.
     37 */
     38 
     39 /**
     40  * @file src/mhd2/h2/h2_reply_funcs.c
     41  * @brief  Definitions of HTTP/2 reply sending functions
     42  * @author Karlson2k (Evgeny Grin)
     43  */
     44 
     45 #include "mhd_sys_options.h"
     46 
     47 #include "sys_bool_type.h"
     48 #include "sys_base_types.h"
     49 
     50 #include "mhd_str_macros.h"
     51 
     52 #include "mhd_constexpr.h"
     53 
     54 #include "mhd_assert.h"
     55 #include "mhd_unreachable.h"
     56 
     57 #include "mhd_buffer.h"
     58 #include "mhd_response.h"
     59 #include "mhd_connection.h"
     60 #include "mhd_daemon.h"
     61 
     62 #include "mhd_str.h"
     63 #include "mhd_read_file.h"
     64 
     65 #include "stream_process_reply.h"
     66 
     67 #include "h2_conn_data.h"
     68 #include "h2_stream_data.h"
     69 
     70 #include "h2_frame_init.h"
     71 #include "h2_proc_conn.h"
     72 #include "h2_proc_out.h"
     73 
     74 #include "h2_frame_codec.h"
     75 
     76 #include "hpack/mhd_hpack_codec.h"
     77 
     78 
     79 #include "h2_reply_funcs.h"
     80 
     81 struct mhd_H2Stream;    /* Forward declaration */
     82 
     83 /* local wrapper */
     84 mhd_static_inline MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_NONNULL_ALL_
     85 MHD_FN_PAR_INOUT_ (1) MHD_FN_PAR_IN_ (2) MHD_FN_PAR_IN_ (3)
     86 MHD_FN_PAR_OUT_SIZE_ (6, 5) MHD_FN_PAR_OUT_ (7) bool
     87 enc_field (struct mhd_HpackEncContext *restrict hk_enc,
     88            const struct mhd_BufferConst *restrict name,
     89            const struct mhd_BufferConst *restrict value,
     90            enum mhd_HpackEncPolicy enc_pol,
     91            const size_t out_buff_size,
     92            uint8_t *restrict out_buff,
     93            size_t *restrict bytes_encoded)
     94 {
     95   enum mhd_HpackEncResult enc_res;
     96 
     97   enc_res = mhd_hpack_enc_field (hk_enc,
     98                                  name,
     99                                  value,
    100                                  enc_pol,
    101                                  out_buff_size,
    102                                  out_buff,
    103                                  bytes_encoded);
    104 
    105   mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res);
    106 
    107   return (mhd_HPACK_ENC_RES_OK == enc_res);
    108 }
    109 
    110 
    111 static MHD_FN_PAR_NONNULL_ALL_ size_t
    112 stream_headers_encode (struct mhd_H2Stream *restrict s,
    113                        struct mhd_Buffer *restrict pl,
    114                        bool *restrict fields_complete)
    115 {
    116   struct mhd_HpackEncContext *const hk_enc = &(s->c->h2.hk_enc);
    117   struct MHD_Response *const r = s->rpl.response;
    118   uint8_t *restrict buff = (uint8_t *)pl->data;
    119   size_t pos;
    120   size_t pos_incr;
    121   size_t fld_num;
    122   enum mhd_HpackEncResult enc_res;
    123   struct mhd_ResponseHeader *hdr;
    124 
    125   *fields_complete = false; /* Could be updated at the end */
    126   pos = 0u;
    127   fld_num = 0u;
    128 
    129   /* Pseudo-header */
    130   if (fld_num >= s->rpl.fields.num_sent)
    131   {
    132     enc_res = mhd_hpack_enc_ph_status (hk_enc,
    133                                        (uint_fast16_t)s->rpl.response->sc,
    134                                        mhd_HPACK_ENC_PFS_POL_NORMAL,
    135                                        pl->size - pos,
    136                                        buff + pos,
    137                                        &pos_incr);
    138     mhd_assert (mhd_HPACK_ENC_RES_ALLOC_ERR != enc_res);
    139     if (mhd_HPACK_ENC_RES_OK != enc_res)
    140       return pos;
    141 
    142     pos += pos_incr;
    143     ++(s->rpl.fields.num_sent);
    144   }
    145   ++fld_num;
    146 
    147   /* "date" header */
    148 
    149   if ((!r->cfg.has_hdr_date)
    150       && (!s->c->daemon->req_cfg.suppress_date))
    151   {
    152     if (fld_num >= s->rpl.fields.num_sent)
    153     {
    154       char val_buff[30];
    155       if (mhd_build_date_str (val_buff))
    156       {
    157         static const struct mhd_BufferConst hdr_name =
    158           mhd_MSTR_INIT ("date");
    159         struct mhd_BufferConst hdr_val;
    160 
    161         hdr_val.data = val_buff;
    162         hdr_val.size = 29u;
    163 
    164         if (!enc_field (hk_enc,
    165                         &hdr_name,
    166                         &hdr_val,
    167                         (1 >= s->c->h2.streams.num_streams) ?
    168                         mhd_HPACK_ENC_POL_LOW_PRIO : mhd_HPACK_ENC_POL_NEUTRAL,
    169                         pl->size - pos,
    170                         buff + pos,
    171                         &pos_incr))
    172           return pos;
    173 
    174         pos += pos_incr;
    175         ++(s->rpl.fields.num_sent);
    176       }
    177     }
    178     ++fld_num;
    179   }
    180 
    181   /* "content-length" header */
    182   if (s->rpl.fields.auto_cntn_len)
    183   {
    184     if (fld_num >= s->rpl.fields.num_sent)
    185     {
    186       static const struct mhd_BufferConst hdr_name =
    187         mhd_MSTR_INIT ("content-length");
    188       char val_buff[21]; /* Maximum supported value is 18446744073709551615 */
    189       struct mhd_BufferConst hdr_val;
    190 
    191       mhd_assert (MHD_SIZE_UNKNOWN > r->cntn_size);
    192       hdr_val.data = val_buff;
    193       hdr_val.size = mhd_uint64_to_str (r->cntn_size,
    194                                         val_buff,
    195                                         sizeof(val_buff));
    196       mhd_assert (0u != hdr_val.size);
    197 
    198       if (!enc_field (hk_enc,
    199                       &hdr_name,
    200                       &hdr_val,
    201                       r->reuse.reusable ?
    202                       mhd_HPACK_ENC_POL_NEUTRAL : mhd_HPACK_ENC_POL_LOW_PRIO,
    203                       pl->size - pos,
    204                       buff + pos,
    205                       &pos_incr))
    206         return pos;
    207 
    208       pos += pos_incr;
    209       ++(s->rpl.fields.num_sent);
    210     }
    211     ++fld_num;
    212   }
    213 
    214   /* User headers */
    215 
    216   for (hdr = mhd_DLINKEDL_GET_FIRST (r, headers);
    217        NULL != hdr;
    218        hdr = mhd_DLINKEDL_GET_NEXT (hdr, headers))
    219   {
    220     if (NULL == hdr->h2.name.data)
    221       continue; /* The header is HTTP/1.x only */
    222 
    223     if (fld_num >= s->rpl.fields.num_sent)
    224     {
    225       if (!enc_field (hk_enc,
    226                       &(hdr->h2.name),
    227                       &(hdr->h2.value),
    228                       mhd_HPACK_ENC_POL_NEUTRAL,
    229                       pl->size - pos,
    230                       buff + pos,
    231                       &pos_incr))
    232         return pos;
    233       pos += pos_incr;
    234       ++(s->rpl.fields.num_sent);
    235     }
    236     ++fld_num;
    237   }
    238 
    239   *fields_complete = true;
    240   return pos;
    241 }
    242 
    243 
    244 static MHD_FN_PAR_NONNULL_ALL_ bool
    245 stream_headers_send (struct mhd_H2Stream *s)
    246 {
    247   union mhd_H2FrameUnion h2frame;
    248   struct mhd_H2FrameHeadersInfo *hdrs;
    249   struct mhd_H2FrameContinuationInfo *cont;
    250   struct mhd_Buffer buff;
    251   struct mhd_Buffer payload;
    252   size_t payload_offset;
    253   bool *complete_header;
    254   size_t payload_used;
    255 
    256   mhd_assert (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage);
    257 
    258   if (0u == s->rpl.fields.num_sent)
    259   {
    260     hdrs = mhd_h2_frame_init_headers (&h2frame,
    261                                       s->stream_id,
    262                                       false, /* could be updated below */
    263                                       !s->rpl.send_content);
    264     cont = NULL;
    265     complete_header = &(hdrs->end_headers);
    266   }
    267   else
    268   {
    269     hdrs = NULL;
    270     cont = mhd_h2_frame_init_continuation (&h2frame,
    271                                            s->stream_id,
    272                                            false); /* could be updated below */
    273     complete_header = &(cont->end_headers);
    274   }
    275 
    276   if (!mhd_h2_out_buff_acquire_fr_w_payload (s->c,
    277                                              &h2frame,
    278                                              &buff,
    279                                              &payload_offset))
    280     return false;
    281 
    282   payload.data = buff.data + payload_offset;
    283   payload.size = buff.size - payload_offset;
    284 
    285   payload_used = stream_headers_encode (s,
    286                                         &payload,
    287                                         complete_header);
    288 
    289   if (0u != payload_used)
    290   {
    291     const size_t full_fr_size =  mhd_h2_frame_set_payload_size (&h2frame,
    292                                                                 payload_used);
    293     const size_t final_fr_hdr_size =
    294       mhd_h2_frame_hdr_encode (&h2frame,
    295                                payload_offset,
    296                                (uint8_t *)buff.data);
    297     mhd_assert (payload_offset == final_fr_hdr_size);
    298     (void)final_fr_hdr_size;
    299 
    300     mhd_h2_out_buff_unlock (s->c,
    301                             full_fr_size);
    302     if (*complete_header)
    303     {
    304       s->rpl.stage = s->rpl.send_content ?
    305                      mhd_H2_RPL_STAGE_HEADERS_COMPLETE :
    306                      mhd_H2_RPL_STAGE_END_STREAM;
    307     }
    308     return true; /* Success exit point */
    309   }
    310 
    311   mhd_h2_out_buff_unlock (s->c,
    312                           0u);
    313 
    314   if (((s->c->write_buffer_size - s->c->write_buffer_append_offset) >=
    315        mhd_H2_FR_HDR_BASE_SIZE + s->c->h2.peer.max_frame_size)
    316       || (0u == s->c->write_buffer_append_offset))
    317   {
    318     /* The output buffer may contain the maximum size frame, but no single
    319        header has been added. It makes no sense to wait more as the
    320        response header is too large to be used in this connection. */
    321     s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR;
    322     s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
    323     return false;
    324   }
    325 
    326   return false;
    327 }
    328 
    329 
    330 static MHD_FN_PAR_NONNULL_ALL_
    331 MHD_FN_PAR_OUT_SIZE_ (4, 3)
    332 MHD_FN_PAR_OUT_ (5) bool
    333 content_read_iovec (struct MHD_Response *restrict r,
    334                     uint_fast64_t offset,
    335                     size_t buff_size,
    336                     uint8_t *restrict buff,
    337                     size_t *restrict written)
    338 {
    339   size_t i;
    340   uint_fast64_t skipped;
    341   const mhd_iovec *const restrict iov = r->cntn.iovec.iov;
    342 
    343   mhd_assert (mhd_RESPONSE_CONTENT_DATA_IOVEC == r->cntn_dtype);
    344 
    345   skipped = 0u;
    346 
    347   for (i = 0u; r->cntn.iovec.cnt > i; ++i)
    348   {
    349     if (skipped + iov[i].iov_len > offset)
    350       break;
    351     skipped += iov[i].iov_len;
    352     mhd_assert (skipped >= iov[i].iov_len);
    353   }
    354 
    355   if (r->cntn.iovec.cnt == i)
    356     return false;
    357 
    358   if (1)
    359   {
    360     size_t elmnt_copy;
    361     const size_t elmnt_off = (size_t)(offset - skipped);
    362 
    363     if (elmnt_off != (offset - skipped))
    364       return false;
    365 
    366     mhd_assert (0u != iov[i].iov_len);
    367 
    368     elmnt_copy = (size_t)(iov[i].iov_len - elmnt_off);
    369     if (buff_size < elmnt_copy)
    370       elmnt_copy = buff_size;
    371 
    372     memcpy (buff,
    373             ((const uint8_t *)iov[i].iov_base) + elmnt_off,
    374             elmnt_copy);
    375     *written = elmnt_copy;
    376 
    377     if (elmnt_copy == buff_size)
    378       return true;
    379 
    380     ++i;
    381   }
    382 
    383   for ((void)i; r->cntn.iovec.cnt > i; ++i)
    384   {
    385     mhd_assert (0u != iov[i].iov_len);
    386     if ((buff_size - *written) <= iov[i].iov_len)
    387     {
    388       memcpy (buff + *written,
    389               iov[i].iov_base,
    390               buff_size - *written);
    391       *written = buff_size;
    392       return true;
    393     }
    394     memcpy (buff + *written,
    395             iov[i].iov_base,
    396             (size_t)iov[i].iov_len);
    397     *written += (size_t)iov[i].iov_len;
    398     mhd_assert (*written > iov[i].iov_len);
    399   }
    400   return true;
    401 }
    402 
    403 
    404 static MHD_FN_PAR_NONNULL_ALL_
    405 MHD_FN_PAR_OUT_SIZE_ (4, 3)
    406 MHD_FN_PAR_OUT_ (5) enum mhd_FileReadResult
    407 content_read_file (struct MHD_Response *restrict r,
    408                    uint_fast64_t offset,
    409                    size_t buff_size,
    410                    uint8_t *restrict buff,
    411                    size_t *restrict written)
    412 {
    413   enum mhd_FileReadResult res;
    414   uint_fast64_t file_off;
    415 
    416   mhd_assert (mhd_RESPONSE_CONTENT_DATA_FILE == r->cntn_dtype);
    417   // TODO: support pipe reading without position
    418   mhd_assert (!r->cntn.file.is_pipe);
    419 
    420   file_off = offset + r->cntn.file.offset;
    421   if (file_off < offset)
    422     return mhd_FILE_READ_OFFSET_TOO_LARGE; /* Offset too large */
    423 
    424   res = mhd_read_file (r->cntn.file.fd,
    425                        file_off,
    426                        buff_size,
    427                        (char *)buff,
    428                        written);
    429   if ((mhd_FILE_READ_EOF == res)
    430       && (r->cntn_size != MHD_SIZE_UNKNOWN))
    431     res = mhd_FILE_READ_ERROR; /* Unexpected EOF */
    432 
    433   return res;
    434 }
    435 
    436 
    437 mhd_constexpr uint_least32_t min_size_for_data = 128u;
    438 
    439 static MHD_FN_PAR_NONNULL_ALL_ bool
    440 stream_content_send (struct mhd_H2Stream *s)
    441 {
    442   struct MHD_Response *const r = s->rpl.response;
    443   union mhd_H2FrameUnion h2frame;
    444   struct mhd_H2FrameDataInfo *dat;
    445   struct mhd_Buffer buff;
    446   uint8_t *pld_buff;
    447   size_t pld_buff_size;
    448   size_t cntnt_left;
    449   size_t payload_offset;
    450   size_t payload_used;
    451   int_least32_t wndw_limit;
    452   uint_least32_t full_payload_limit;
    453   uint_least32_t payload_size_limit;
    454 
    455   mhd_assert (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage);
    456   mhd_assert (s->rpl.send_content);
    457   mhd_assert (0u != r->cntn_size);
    458   mhd_assert (!r->cfg.head_only);
    459 
    460   if (s->c->h2.state.send_window < s->state.send_window)
    461     wndw_limit = s->c->h2.state.send_window;
    462   else
    463     wndw_limit = s->state.send_window;
    464 
    465   if (0 >= wndw_limit)
    466     return false; /* The peer should increment window(s) first */
    467 
    468   full_payload_limit = (uint_least32_t)wndw_limit;
    469   if (MHD_SIZE_UNKNOWN != r->cntn_size)
    470   {
    471     cntnt_left = r->cntn_size - s->rpl.cntn_read_pos;
    472     if (cntnt_left < full_payload_limit)
    473       full_payload_limit = (uint_least32_t)cntnt_left;
    474   }
    475   else
    476     cntnt_left = MHD_SIZE_UNKNOWN;
    477 
    478   dat = mhd_h2_frame_init_data (&h2frame,
    479                                 s->stream_id,
    480                                 false);  /* could be updated below */
    481 
    482   if (!mhd_h2_out_buff_acquire_fr_w_payload_l (s->c,
    483                                                &h2frame,
    484                                                full_payload_limit,
    485                                                &buff,
    486                                                &payload_offset))
    487     return false;
    488 
    489   pld_buff = (uint8_t *)buff.data + payload_offset;
    490   pld_buff_size = buff.size - payload_offset;
    491   mhd_assert (mhd_H2_FR_HDR_BASE_SIZE <= payload_offset);
    492 
    493   /* The acquired buffer could be smaller than the requested limit.
    494      The payload of this frame is limited by the acquired buffer. */
    495   payload_size_limit = full_payload_limit;
    496   if (pld_buff_size < payload_size_limit)
    497     payload_size_limit = (uint_least32_t)pld_buff_size;
    498 
    499   if ((min_size_for_data > payload_size_limit)
    500       && (cntnt_left != payload_size_limit)
    501       && (mhd_RESPONSE_CONTENT_DATA_CALLBACK != r->cntn_dtype))
    502   { /* The acquired space is too small to be used efficiently.
    503        The callback content is not limited: the application may provide
    504        the data in small portions. */
    505     mhd_h2_out_buff_unlock (s->c,
    506                             0u);
    507     return false;
    508   }
    509 
    510   mhd_assert (r->cntn_size > s->rpl.cntn_read_pos);
    511 
    512   payload_used = 0u;
    513   switch (r->cntn_dtype)
    514   {
    515   case mhd_RESPONSE_CONTENT_DATA_BUFFER:
    516     payload_used = (size_t)payload_size_limit;
    517     memcpy (pld_buff,
    518             r->cntn.buf + s->rpl.cntn_read_pos,
    519             payload_used);
    520     break;
    521   case mhd_RESPONSE_CONTENT_DATA_IOVEC:
    522     if (!content_read_iovec (r,
    523                              s->rpl.cntn_read_pos,
    524                              (size_t)payload_size_limit,
    525                              pld_buff,
    526                              &payload_used))
    527       payload_used = 0u;
    528     break;
    529   case mhd_RESPONSE_CONTENT_DATA_FILE:
    530     switch (content_read_file (r,
    531                                s->rpl.cntn_read_pos,
    532                                (size_t)payload_size_limit,
    533                                pld_buff,
    534                                &payload_used))
    535     {
    536     case mhd_FILE_READ_OK:
    537       break;
    538     case mhd_FILE_READ_ERROR:
    539     case mhd_FILE_READ_OFFSET_TOO_LARGE:
    540       payload_used = 0u; // TODO: handle it better
    541       break;
    542     case mhd_FILE_READ_EOF:
    543       payload_used = 0u;
    544       dat->end_stream = true;
    545       break;
    546     default:
    547       mhd_UNREACHABLE ();
    548       payload_used = 0u;
    549       break;
    550     }
    551     break;
    552   case mhd_RESPONSE_CONTENT_DATA_CALLBACK:
    553     s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
    554     break;
    555   case mhd_RESPONSE_CONTENT_DATA_INVALID:
    556   default:
    557     mhd_UNREACHABLE ();
    558     s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
    559     break;
    560   }
    561 
    562   dat->end_stream = dat->end_stream
    563                     || (payload_used + s->rpl.cntn_read_pos == r->cntn_size);
    564 
    565   if ((0u != payload_used)
    566       || (dat->end_stream))
    567   {
    568     const size_t full_fr_size =  mhd_h2_frame_set_payload_size (&h2frame,
    569                                                                 payload_used);
    570     const size_t final_fr_hdr_size =
    571       mhd_h2_frame_hdr_encode (&h2frame,
    572                                payload_offset,
    573                                (uint8_t *)buff.data);
    574     mhd_assert (payload_offset == final_fr_hdr_size);
    575     (void)final_fr_hdr_size;
    576 
    577     mhd_h2_out_buff_unlock (s->c,
    578                             full_fr_size);
    579     s->c->h2.state.send_window -=
    580       (int_least32_t)(full_fr_size - mhd_H2_FR_HDR_BASE_SIZE);
    581     mhd_assert (0 <= s->c->h2.state.send_window);
    582     s->state.send_window -=
    583       (int_least32_t)(full_fr_size - mhd_H2_FR_HDR_BASE_SIZE);
    584     mhd_assert (0 <= s->state.send_window);
    585 
    586     /* The data has been put to the frame, advance the reading position.
    587        The 'end_stream' flag has been calculated before the advancing. */
    588     s->rpl.cntn_read_pos += payload_used;
    589     if (dat->end_stream)
    590       s->rpl.stage = mhd_H2_RPL_STAGE_END_STREAM;
    591 
    592     return true; /* Success exit point */
    593   }
    594 
    595   mhd_h2_out_buff_unlock (s->c,
    596                           0u);
    597 
    598   s->state.mhd_err = mhd_H2_ERR_INTERNAL_ERROR;
    599   s->rpl.stage = mhd_H2_RPL_STAGE_BROKEN;
    600 
    601   return false;
    602 
    603 }
    604 
    605 
    606 MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool
    607 mhd_h2_stream_reply_send (struct mhd_H2Stream *s)
    608 {
    609   mhd_assert (mhd_HNDL_IS_HTTP2 (s));
    610   mhd_assert (mhd_H2_RPL_STAGE_END_STREAM != s->rpl.stage);
    611   mhd_assert (mhd_H2_RPL_STAGE_BROKEN != s->rpl.stage);
    612 
    613   if (mhd_H2_RPL_STAGE_HEADERS_INCOMPLETE == s->rpl.stage)
    614   {
    615     if (!mhd_hpack_enc_dyn_resize (&(s->c->h2.hk_enc)))
    616     {
    617       /* Ignore failure of the next function as the connection and stream
    618          will be retried next round if connection is not aborted. */
    619       mhd_h2_conn_finish (s->c,
    620                           mhd_H2_ERR_INTERNAL_ERROR,
    621                           false);
    622       return false;
    623     }
    624 
    625     if (!stream_headers_send (s))
    626       return false;
    627 
    628     if ((mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage)
    629         && (mhd_RESPONSE_CONTENT_DATA_FILE <= s->rpl.response->cntn_dtype))
    630       return true; /* Do not combine with content sending as the data is not ready yet */
    631   }
    632 
    633   if (mhd_H2_RPL_STAGE_HEADERS_COMPLETE == s->rpl.stage)
    634   {
    635     if (!stream_content_send (s))
    636       return false;
    637   }
    638 
    639   return true;
    640 
    641 }