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 }