libmicrohttpd2

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

upgrade_prep.c (15362B)


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