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 }