libmicrohttpd2

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

upgrade_prep.c (15655B)


      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) 2024 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/upgrade_prep.c
     41  * @brief  The implementation of functions for preparing for MHD Action for
     42  *         HTTP-Upgrade
     43  * @author Karlson2k (Evgeny Grin)
     44  */
     45 
     46 #include "mhd_sys_options.h"
     47 
     48 #include <string.h>
     49 
     50 #include "sys_bool_type.h"
     51 #include "sys_base_types.h"
     52 
     53 #include "upgrade_prep.h"
     54 
     55 #include "mhd_cntnr_ptr.h"
     56 
     57 #include "mhd_str_types.h"
     58 #include "mhd_str_macros.h"
     59 
     60 #include "mhd_assert.h"
     61 #include "mhd_str.h"
     62 
     63 #include "daemon_logger.h"
     64 
     65 #include "mhd_request.h"
     66 #include "mhd_connection.h"
     67 
     68 #include "mhd_upgrade.h"
     69 #include "stream_funcs.h"
     70 
     71 #include "mhd_public_api.h"
     72 
     73 /**
     74  * Check whether the provided data fits the buffer and append provided data
     75  * to the buffer
     76  * @param buf_size the size of the @a buf buffer
     77  * @param buf the buffer to use, must not be NULL
     78  * @param[in,out] pbuf_used the pointer to the variable with current offset in
     79  *                          the @a buf buffer, updated if @a copy_data is added
     80  * @param copy_size the size of the @a copy_data, must be non-zero
     81  * @param copy_data the data to append to the buffer, must not be NULL
     82  * @return 'true' if @a copy_data has been appended to the @a buf buffer,
     83  *         'false' if @a buf buffer has not enough space
     84  */
     85 mhd_static_inline MHD_FN_PAR_NONNULL_ALL_
     86 MHD_FN_PAR_OUT_SIZE_ (2, 1)
     87 MHD_FN_PAR_INOUT_ (3)
     88 MHD_FN_PAR_IN_SIZE_ (5, 4) bool
     89 buf_append (size_t buf_size,
     90             char *buf,
     91             size_t *restrict pbuf_used,
     92             size_t copy_size,
     93             const char copy_data[MHD_FN_PAR_DYN_ARR_SIZE_ (copy_size)])
     94 {
     95   mhd_assert (0 != copy_size);
     96 
     97   if ((*pbuf_used + copy_size > buf_size)
     98       || (((size_t)(*pbuf_used + copy_size)) < copy_size))
     99     return false;
    100 
    101   memcpy (buf + *pbuf_used, copy_data, copy_size);
    102   *pbuf_used += copy_size;
    103 
    104   return true;
    105 }
    106 
    107 
    108 /**
    109  * The build_reply_header() results
    110  */
    111 enum mhd_UpgradeHeaderBuildRes
    112 {
    113   /**
    114    * Success
    115    */
    116   MHD_UPGRADE_HDR_BUILD_OK = 0
    117   ,
    118   /**
    119    * Not enough buffer size (not logged)
    120    */
    121   MHD_UPGRADE_HDR_BUILD_NO_MEM
    122   ,
    123   /**
    124    * Some other error (already logged)
    125    */
    126   MHD_UPGRADE_HDR_BUILD_OTHER_ERR
    127 };
    128 
    129 /**
    130  * Build full reply header for the upgrade action.
    131  * The reply header serves as a preamble, as soon as it sent the connection
    132  * switched to the "upgraded" mode.
    133  * @param c the connection to use
    134  * @param buf_size the size of the @a buf buffer
    135  * @param[out] buf the buffer to build the reply,
    136  *                 can be NULL if @a buf_size is zero
    137  * @param[out] pbuf_used the pointer to the variable receiving the size of
    138  *                       the reply header in the @a buf buffer
    139  * @param upgrade_hdr_value the value of the "Upgrade:" reply header
    140  * @param num_headers the number of elements in the @a headers array,
    141  *                    must be zero if @a headers is NULL
    142  * @param headers the array of string pairs used as reply headers,
    143  *        can be NULL
    144  * @return #MHD_UPGRADE_HDR_BUILD_OK if reply has been built successfully and
    145  *         @a pbuf_used has been updated,
    146  *         #MHD_UPGRADE_HDR_BUILD_NO_MEM if @a buf has not enough space to build
    147  *         the reply header (the error is not yet logged),
    148  *         #MHD_UPGRADE_HDR_BUILD_OTHER_ERR if any other error occurs (the error
    149  *         has been logged)
    150  */
    151 static MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_IF_NONZERO_ (3, 2)
    152 MHD_FN_PAR_NONNULL_ (4) MHD_FN_PAR_NONNULL_ (5)
    153 MHD_FN_PAR_OUT_SIZE_ (3, 2) MHD_FN_PAR_OUT_ (4) MHD_FN_PAR_CSTR_ (5)
    154 MHD_FN_PAR_NONNULL_IF_NONZERO_ (7, 6)
    155 MHD_FN_PAR_IN_SIZE_ (7, 6) enum mhd_UpgradeHeaderBuildRes
    156 build_reply_header (struct MHD_Connection *restrict c,
    157                     const size_t buf_size,
    158                     char *buf,
    159                     size_t *pbuf_used,
    160                     const char *restrict upgrade_hdr_value,
    161                     size_t num_headers,
    162                     const struct MHD_NameValueCStr *restrict headers)
    163 {
    164   static const struct MHD_String rp_100_cntn_msg =
    165     mhd_MSTR_INIT (mdh_HTTP_1_1_100_CONTINUE_REPLY);
    166   static const struct MHD_String status_line =
    167     mhd_MSTR_INIT (MHD_HTTP_VERSION_1_1_STR " 101 Switching Protocols\r\n");
    168   static const struct MHD_String upgrade_hdr_start =
    169     mhd_MSTR_INIT (MHD_HTTP_HEADER_UPGRADE ": ");
    170   size_t upgrade_hdr_value_len;
    171   size_t buf_used;
    172   size_t i;
    173   bool has_conn_hdr;
    174   bool hdr_name_invalid;
    175 
    176   mhd_assert (MHD_HTTP_VERSION_IS_LIKE_11 (c->rq.http_ver));
    177   mhd_assert ((0 == c->rq.cntn.cntn_size) \
    178               || (mhd_HTTP_STAGE_FULL_REQ_RECEIVED == c->stage));
    179 
    180   if (0 == buf_size)
    181     return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    182 
    183   buf_used = 0;
    184 
    185   if (c->rq.have_expect_100 && !c->rp.sent_100_cntn)
    186   {
    187     /* Must send "100 Continue" before switching to data pumping */
    188     if (!buf_append (buf_size,
    189                      buf,
    190                      &buf_used,
    191                      rp_100_cntn_msg.len,
    192                      rp_100_cntn_msg.cstr))
    193       return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    194   }
    195 
    196   /* Status line */
    197   if (!buf_append (buf_size,
    198                    buf,
    199                    &buf_used,
    200                    status_line.len,
    201                    status_line.cstr))
    202     return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    203 
    204   /* "Upgrade:" header */
    205   if (!buf_append (buf_size,
    206                    buf,
    207                    &buf_used,
    208                    upgrade_hdr_start.len,
    209                    upgrade_hdr_start.cstr))
    210     return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    211 
    212   upgrade_hdr_value_len = strcspn (upgrade_hdr_value,
    213                                    "\n\r");
    214   if ((0 == upgrade_hdr_value_len)
    215       || (0 != upgrade_hdr_value[upgrade_hdr_value_len]))
    216   {
    217     mhd_LOG_MSG (c->daemon, \
    218                  MHD_SC_RESP_HEADER_VALUE_INVALID, \
    219                  "The provided value of the \"Upgrade:\" header " \
    220                  "is invalid.");
    221     return MHD_UPGRADE_HDR_BUILD_OTHER_ERR;
    222   }
    223   if ((buf_used + upgrade_hdr_value_len + 2 > buf_size)
    224       || (((size_t)(buf_used + upgrade_hdr_value_len + 2)) < buf_used))
    225     return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    226   memcpy (buf + buf_used,
    227           upgrade_hdr_value,
    228           upgrade_hdr_value_len);
    229   buf_used += upgrade_hdr_value_len;
    230   buf[buf_used++] = '\r';
    231   buf[buf_used++] = '\n';
    232 
    233   /* User headers */
    234   has_conn_hdr = false;
    235   hdr_name_invalid = false;
    236   for (i = 0; i < num_headers; ++i)
    237   {
    238     static const struct MHD_String conn_hdr_prefix =
    239       mhd_MSTR_INIT ("upgrade, ");
    240     size_t hdr_name_len;
    241     size_t hdr_value_len;
    242     size_t line_len;
    243     bool is_conn_hdr;
    244 
    245     if (NULL == headers[i].name)
    246     {
    247       hdr_name_invalid = true;
    248       break;
    249     }
    250 
    251     hdr_name_len = strcspn (headers[i].name,
    252                             "\n\r \t:,;\"");
    253 
    254     if ((0 == hdr_name_len)
    255         || (0 != headers[i].name[hdr_name_len]))
    256     {
    257       hdr_name_invalid = true;
    258       break;
    259     }
    260 
    261     if (NULL == headers[i].value)
    262       break;
    263 
    264     hdr_value_len = strcspn (headers[i].value,
    265                              "\n\r");
    266 
    267     if (0 != headers[i].value[hdr_value_len])
    268       break;
    269 
    270     if (mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_UPGRADE, \
    271                                      headers[i].name, \
    272                                      hdr_name_len))
    273       break;
    274 
    275     line_len = hdr_name_len + 2 + hdr_value_len + 2;
    276 
    277     is_conn_hdr =
    278       mhd_str_equal_caseless_n_st (MHD_HTTP_HEADER_CONNECTION, \
    279                                    headers[i].name, \
    280                                    hdr_name_len);
    281     if (is_conn_hdr)
    282     {
    283       if (0 == hdr_value_len)
    284         continue; /* Skip the header, proper "Connection:" header will be added below */
    285       if (has_conn_hdr)
    286         break; /* Two "Connection:" headers */
    287       has_conn_hdr = true;
    288 
    289       if (mhd_str_has_s_token_caseless (headers[i].value, "close"))
    290         break;
    291       if (mhd_str_has_s_token_caseless (headers[i].value, "keep-alive"))
    292         break;
    293 
    294       line_len += conn_hdr_prefix.len;
    295       if (line_len < conn_hdr_prefix.len)
    296         return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    297     }
    298 
    299     if ((buf_used + line_len > buf_size)
    300         || (((size_t)(buf_used + line_len)) < line_len)
    301         || (line_len < hdr_value_len))
    302       return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    303 
    304     memcpy (buf + buf_used,
    305             headers[i].name,
    306             hdr_name_len);
    307     buf_used += hdr_name_len;
    308     buf[buf_used++] = ':';
    309     buf[buf_used++] = ' ';
    310 
    311     if (is_conn_hdr)
    312     {
    313       memcpy (buf + buf_used,
    314               conn_hdr_prefix.cstr,
    315               conn_hdr_prefix.len);
    316       buf_used += conn_hdr_prefix.len;
    317     }
    318 
    319     memcpy (buf + buf_used,
    320             headers[i].value,
    321             hdr_value_len);
    322     buf_used += hdr_value_len;
    323     buf[buf_used++] = '\r';
    324     buf[buf_used++] = '\n';
    325   }
    326   mhd_assert (buf_size >= buf_used);
    327   mhd_assert (!hdr_name_invalid || (i < num_headers));
    328 
    329   if (i < num_headers)
    330   {
    331     if (hdr_name_invalid)
    332       mhd_LOG_PRINT (c->daemon, \
    333                      MHD_SC_RESP_HEADER_NAME_INVALID, \
    334                      mhd_LOG_FMT ("The name of the provided header " \
    335                                   "number %lu is invalid. " \
    336                                   "Header name: '%s'. " \
    337                                   "Header Value: '%s'."),
    338                      (unsigned long)i,
    339                      headers[i].name ? headers[i].name : "(NULL)",
    340                      headers[i].value ? headers[i].value : "(NULL)");
    341     else
    342       mhd_LOG_PRINT (c->daemon, \
    343                      MHD_SC_RESP_HEADER_VALUE_INVALID, \
    344                      mhd_LOG_FMT ("The value of the provided header " \
    345                                   "number %lu is invalid. " \
    346                                   "Header name: '%s'. " \
    347                                   "Header Value: '%s'."),
    348                      (unsigned long)i,
    349                      headers[i].name ? headers[i].name : "(NULL)",
    350                      headers[i].value ? headers[i].value : "(NULL)");
    351 
    352     return MHD_UPGRADE_HDR_BUILD_OTHER_ERR;
    353   }
    354 
    355   /* "Connection:" header (if has not been added already) */
    356   if (!has_conn_hdr)
    357   {
    358     static const struct MHD_String conn_hdr_line =
    359       mhd_MSTR_INIT (MHD_HTTP_HEADER_CONNECTION ": upgrade\r\n");
    360 
    361     if (!buf_append (buf_size,
    362                      buf,
    363                      &buf_used,
    364                      conn_hdr_line.len,
    365                      conn_hdr_line.cstr))
    366       return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    367   }
    368 
    369   /* End of reply header */
    370   if ((buf_used + 2 > buf_size)
    371       || (((size_t)(buf_used + 2)) < 2))
    372     return MHD_UPGRADE_HDR_BUILD_NO_MEM;
    373 
    374   buf[buf_used++] = '\r';
    375   buf[buf_used++] = '\n';
    376 
    377   mhd_assert (buf_size >= buf_used);
    378   *pbuf_used = buf_used;
    379 
    380   return MHD_UPGRADE_HDR_BUILD_OK;
    381 }
    382 
    383 
    384 /**
    385  * Prepare connection to be used with the HTTP "Upgrade" action
    386  * @param c the connection object
    387  * @param upgrade_hdr_value the value of the "Upgrade:" header, mandatory
    388  *                          string
    389  * @param num_headers number of elements in the @a headers array,
    390  *                    must be zero if @a headers is NULL
    391  * @param headers the optional pointer to the array of the headers (the strings
    392  *                are copied and does not need to be valid after return from
    393  *                this function),
    394  *                can be NULL if @a num_headers is zero
    395  * @return 'true' if succeed,
    396  *         'false' otherwise
    397  */
    398 static MHD_FN_PAR_NONNULL_ (1)
    399 MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
    400 MHD_FN_PAR_NONNULL_IF_NONZERO_ (4, 3)
    401 MHD_FN_PAR_IN_SIZE_ (4, 3) bool
    402 connection_prepare_for_upgrade (
    403   struct MHD_Connection *restrict c,
    404   const char *restrict upgrade_hdr_value,
    405   size_t num_headers,
    406   const struct MHD_NameValueCStr *restrict headers)
    407 {
    408   enum mhd_UpgradeHeaderBuildRes res;
    409 
    410   mhd_assert (NULL == c->write_buffer);
    411   mhd_assert (0 == c->write_buffer_size);
    412   mhd_assert (0 == c->write_buffer_send_offset);
    413 
    414   mhd_stream_shrink_read_buffer (c);
    415   mhd_stream_maximize_write_buffer (c);
    416   mhd_assert (0 == c->write_buffer_append_offset);
    417 
    418   res = build_reply_header (c,
    419                             c->write_buffer_size,
    420                             c->write_buffer,
    421                             &c->write_buffer_append_offset,
    422                             upgrade_hdr_value,
    423                             num_headers,
    424                             headers);
    425   if (MHD_UPGRADE_HDR_BUILD_OK == res)
    426     return true; /* Success exit point */
    427 
    428   /* Header build failed */
    429   if (MHD_UPGRADE_HDR_BUILD_NO_MEM == res)
    430     mhd_LOG_MSG (c->daemon, \
    431                  MHD_SC_REPLY_HEADERS_TOO_LARGE, \
    432                  "No space in the connection memory pool to create complete " \
    433                  "HTTP \"Upgrade\" response header.");
    434 
    435   mhd_stream_release_write_buffer (c);
    436 
    437   return false;
    438 }
    439 
    440 
    441 MHD_INTERNAL
    442 MHD_FN_PAR_NONNULL_ (1) MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_CSTR_ (2)
    443 MHD_FN_PAR_NONNULL_IF_NONZERO_ (4, 3)
    444 MHD_FN_PAR_IN_SIZE_ (4, 3) bool
    445 mhd_upgrade_prep_for_action (struct MHD_Request *restrict req,
    446                              const char *restrict upgrade_hdr_value,
    447                              size_t num_headers,
    448                              const struct MHD_NameValueCStr *restrict headers,
    449                              bool is_upload_act)
    450 {
    451   struct MHD_Connection *const c =
    452     mhd_CNTNR_PTR (req, struct MHD_Connection, rq);
    453 
    454   mhd_assert (mhd_HTTP_STAGE_HEADERS_PROCESSED <= c->stage);
    455   mhd_assert (mhd_HTTP_STAGE_FULL_REQ_RECEIVED >= c->stage);
    456 
    457   if (req->have_chunked_upload
    458       && (mhd_HTTP_STAGE_FOOTERS_RECEIVED >= c->stage))
    459     return false; /* The request has not been fully received */
    460 
    461   if (!is_upload_act)
    462   {
    463     if (mhd_HTTP_STAGE_HEADERS_PROCESSED != c->stage)
    464       return false;
    465   }
    466   else
    467   {
    468     if (mhd_HTTP_STAGE_BODY_RECEIVING > c->stage)
    469       return false;
    470   }
    471 
    472   return connection_prepare_for_upgrade (c,
    473                                          upgrade_hdr_value,
    474                                          num_headers,
    475                                          headers);
    476 }