test_incompatible.c (31537B)
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 Christian Grothoff 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 test_incompatible.c 41 * @brief tests server rejects incorrect or non-standard requests, either 42 * those that are: 43 * - incompatible to MUST requirements, or 44 * - non-standard and violate SHOULD requirements 45 * @author Christian Grothoff 46 */ 47 #include <stdio.h> 48 #include <stdbool.h> 49 #include <errno.h> 50 #include <string.h> 51 #include <stdlib.h> 52 #include <unistd.h> 53 #include <arpa/inet.h> 54 #include <netinet/ip.h> 55 #include "microhttpd2.h" 56 57 #define LOG 0 58 59 /** 60 * Defines a test. 61 */ 62 struct Test 63 { 64 /** 65 * Human-readable name of the test. NULL to end test array. 66 */ 67 const char *name; 68 69 /** 70 * Request to send to the server. 71 */ 72 const char *upload; 73 74 }; 75 76 77 /** 78 * Tests with HTTP requests that violate MUST constraints 79 * of the HTTP specifications. 80 * 81 * For example, using a bare LF instead of CRLF is forbidden, and 82 * requests that include both a "Transfer-Encoding:" and a 83 * "Content-Length:" headers are rejected. 84 */ 85 static struct Test tests_must[] = { 86 { 87 .name = "HTTP 1.1 without Host", 88 .upload = "GET / HTTP/1.1\r\n\r\n", 89 }, 90 { 91 .name = "HTTP 1.0 GET without CRLF", 92 .upload = "GET / HTTP/1.0\n\n", 93 }, 94 { 95 .name = "POST with both Content-Length and Transfer-Encoding", 96 .upload = 97 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n", 98 }, 99 { 100 .name = "unsupported Ttransfer-Encoding", 101 .upload = 102 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: wild\r\n\r\n0\r\n", 103 }, 104 { 105 .name = "Invalid HTTP version format", 106 .upload = "GET / HTTP/1\r\nHost: example.com\r\n\r\n", 107 // RFC 9112 Section 2.3: HTTP-version must be "HTTP/" followed by two digits separated by "." 108 }, 109 { 110 .name = "Missing space after method", 111 .upload = "GET/ HTTP/1.1\r\nHost: example.com\r\n\r\n", 112 // RFC 9112 Section 3: Request-line requires SP between method and request-target 113 }, 114 { 115 .name = "Invalid request-target with space", 116 .upload = "GET /path with space HTTP/1.1\r\nHost: example.com\r\n\r\n", 117 // RFC 9112 Section 3.2: Request-target must not contain unencoded spaces 118 }, 119 { 120 .name = "Header field name with space", 121 .upload = "GET / HTTP/1.1\r\nHost Name: example.com\r\n\r\n", 122 // RFC 9110 Section 5.1: Field names must be tokens (no spaces allowed) 123 }, 124 #ifdef MORE_PEER_VALIDATION 125 { 126 .name = "Header field name with colon", 127 .upload = "GET / HTTP/1.1\r\nHost:Name: example.com\r\n\r\n", 128 // RFC 9110 Section 5.1: Field names must be tokens (colons not allowed) 129 }, 130 #endif 131 { 132 .name = "Missing colon after header field name", 133 .upload = "GET / HTTP/1.1\r\nHost example.com\r\n\r\n", 134 // RFC 9112 Section 5: Header field must have name, colon, and value 135 }, 136 { 137 .name = "Header line ending with bare CR", 138 .upload = "GET / HTTP/1.1\rHost: example.com\r\n\r\n", 139 // RFC 9112 Section 2.2: Lines must end with CRLF, not bare CR 140 }, 141 { 142 .name = "Request line ending with bare LF", 143 .upload = "GET / HTTP/1.1\nHost: example.com\r\n\r\n", 144 // RFC 9112 Section 2.2: Request-line must end with CRLF 145 }, 146 { 147 .name = "Multiple Host headers", 148 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nHost: other.com\r\n\r\n", 149 // RFC 9112 Section 3.2: A sender MUST NOT generate multiple Host header fields 150 }, 151 { 152 .name = "Negative Content-Length", 153 .upload = 154 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: -5\r\n\r\n", 155 // RFC 9110 Section 8.6: Content-Length value must be non-negative decimal integer 156 }, 157 { 158 .name = "Non-numeric Content-Length", 159 .upload = 160 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: abc\r\n\r\n", 161 // RFC 9110 Section 8.6: Content-Length must be a decimal integer 162 }, 163 { 164 .name = "Multiple Content-Length with different values", 165 .upload = 166 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\nContent-Length: 10\r\n\r\n", 167 // RFC 9110 Section 8.6: Multiple Content-Length values must be identical 168 }, 169 #ifdef MORE_PEER_VALIDATION 170 { 171 .name = "Invalid method with control character", 172 .upload = "GET\x01 / HTTP/1.1\r\nHost: example.com\r\n\r\n", 173 // RFC 9110 Section 9.1: Method token must not contain control characters 174 }, 175 #endif 176 { 177 .name = "Request-target starting with space", 178 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", 179 // RFC 9112 Section 3: Only single SP allowed between method and request-target 180 }, 181 { 182 .name = "HTTP/0.9 simple request with headers", 183 .upload = "GET /\r\nHost: example.com\r\n\r\n", 184 // RFC 9112 Section 2.3: HTTP/0.9 requests must not have headers 185 }, 186 { 187 .name = "Missing final CRLF after headers", 188 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n", 189 // RFC 9112 Section 6.1: Empty line (CRLF) required after headers 190 }, 191 { 192 .name = "Whitespace before header field name", 193 .upload = "GET / HTTP/1.1\r\n Host: example.com\r\n\r\n", 194 // RFC 9112 Section 5: No whitespace allowed before field name 195 }, 196 197 198 { 199 .name = "Empty request line", 200 .upload = "\r\n\r\n", 201 // RFC 9112 Section 3: Request-line is required 202 }, 203 { 204 .name = "Request line with only method", 205 .upload = "GET\r\n\r\n", 206 // RFC 9112 Section 3: Request-line must have method, target, and version 207 }, 208 { 209 .name = "Request line with only method and target", 210 .upload = "GET /\r\n\r\n", 211 // RFC 9112 Section 3: HTTP-version is required in request-line 212 }, 213 { 214 .name = "Request with only CR as line ending", 215 .upload = "GET / HTTP/1.1\rHost: example.com\r\r", 216 // RFC 9112 Section 2.2: CRLF required, not bare CR 217 }, 218 { 219 .name = "Missing space before HTTP version", 220 .upload = "GET /HTTP/1.1\r\nHost: example.com\r\n\r\n", 221 // RFC 9112 Section 3: SP required between request-target and HTTP-version 222 }, 223 { 224 .name = "HTTP version with extra dot", 225 .upload = "GET / HTTP/1.1.0\r\nHost: example.com\r\n\r\n", 226 // RFC 9112 Section 2.3: Version format is "HTTP/" DIGIT "." DIGIT 227 }, 228 { 229 .name = "HTTP version with letter", 230 .upload = "GET / HTTP/1.A\r\nHost: example.com\r\n\r\n", 231 // RFC 9112 Section 2.3: Version numbers must be digits 232 }, 233 #ifdef NOT_A_BUG 234 { 235 .name = "Method with lowercase letters", 236 .upload = "get / HTTP/1.1\r\nHost: example.com\r\n\r\n", 237 // RFC 9110 Section 9.1: Method is case-sensitive 238 /* This a valid non-standard ("custom") method. */ 239 }, 240 #endif 241 #ifdef MORE_PEER_VALIDATION 242 { 243 .name = "Request-target with fragment identifier", 244 .upload = "GET /path#fragment HTTP/1.1\r\nHost: example.com\r\n\r\n", 245 // RFC 9112 Section 3.2.1: Fragment must not be sent in request-target 246 }, 247 #endif 248 #ifdef OTHER_USES 249 { 250 .name = "Absolute-form with userinfo in HTTP/1.1", 251 .upload = 252 "GET http://user:pass@example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n", 253 // RFC 9110 Section 4.2.4: Userinfo (and its "@" delimiter) is now disallowed 254 /* This is a valid string for HTTP proxy */ 255 }, 256 #endif 257 { 258 .name = "Request-target with bare CR", 259 .upload = "GET /path\r/file HTTP/1.1\r\nHost: example.com\r\n\r\n", 260 // RFC 9112 Section 3.2: Request-target must not contain CR 261 }, 262 { 263 .name = "Request-target with LF", 264 .upload = "GET /path\n/file HTTP/1.1\r\nHost: example.com\r\n\r\n", 265 // RFC 9112 Section 3.2: Request-target must not contain LF 266 }, 267 { 268 .name = "Header colon without field name", 269 .upload = "GET / HTTP/1.1\r\n: value\r\nHost: example.com\r\n\r\n", 270 // RFC 9110 Section 5.1: Field name is required before colon 271 }, 272 { 273 .name = "Content-Length with plus sign", 274 .upload = 275 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: +10\r\n\r\n0123456789", 276 // RFC 9110 Section 8.6: Content-Length must be 1*DIGIT (no sign allowed) 277 }, 278 { 279 .name = "Content-Length with whitespace", 280 .upload = 281 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1 0\r\n\r\n0123456789", 282 // RFC 9110 Section 8.6: Content-Length must be digits only 283 }, 284 { 285 .name = "Content-Length with decimal point", 286 .upload = 287 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 10.0\r\n\r\n0123456789", 288 // RFC 9110 Section 8.6: Content-Length must be decimal integer, not floating point 289 }, 290 { 291 .name = "Content-Length overflow value", 292 .upload = 293 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 99999999999999999999999999999\r\n\r\n", 294 // RFC 9110 Section 8.6: Content-Length must be valid decimal integer 295 }, 296 #ifdef MORE_PEER_VALIDATION 297 { 298 .name = "Request with vertical tab in header", 299 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom:\vvalue\r\n\r\n", 300 // RFC 9110 Section 5.5: Only HTAB, SP, and VCHAR allowed in field values (VT is 0x0B) 301 }, 302 #endif 303 #ifdef MORE_PEER_VALIDATION 304 { 305 .name = "Request with form feed in header value", 306 .upload = 307 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom: val\fue\r\n\r\n", 308 // RFC 9110 Section 5.5: Form feed (0x0C) not allowed in field values 309 }, 310 #endif 311 { 312 .name = "Transfer-Encoding with unknown coding", 313 .upload = 314 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: gzip\r\n\r\n", 315 // RFC 9112 Section 6.1: Only 'chunked' and specific codings defined 316 }, 317 { 318 .name = "Transfer-Encoding chunked not last", 319 .upload = 320 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked, gzip\r\n\r\n", 321 // RFC 9112 Section 6.1: 'chunked' must be last when present 322 }, 323 #ifdef SPECIAL_STRICT_PEER_CHECKING 324 { 325 .name = "HTTP/1.0 with Transfer-Encoding", 326 .upload = 327 "POST / HTTP/1.0\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", 328 // RFC 9112 Section 6.1: Transfer-Encoding must not be sent in HTTP/1.0 329 /* RFC does not enforce the server to reject such requests. "Must not be sent" != "Must be rejected" */ 330 }, 331 #endif 332 #ifdef NOT_A_BUG 333 { 334 .name = "POST without Content-Length or Transfer-Encoding", 335 .upload = "POST / HTTP/1.1\r\nHost: example.com\r\n\r\n", 336 // RFC 9112 Section 6.3: Message with body must have Content-Length or Transfer-Encoding 337 /* This is a perfectly valid request with an empty body. */ 338 }, 339 #endif 340 #ifdef MORE_PEER_VALIDATION 341 { 342 .name = "Request with CTL character in header name", 343 .upload = 344 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cu\x01stom: value\r\n\r\n", 345 // RFC 9110 Section 5.1: Field names must be tokens, CTL chars not allowed 346 }, 347 #endif 348 #ifdef MORE_PEER_VALIDATION 349 { 350 .name = "Request with DEL character in header name", 351 .upload = 352 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom\x7F: value\r\n\r\n", 353 // RFC 9110 Section 5.1: DEL (0x7F) not allowed in field names 354 }, 355 #endif 356 #ifdef MORE_PEER_VALIDATION 357 { 358 .name = "Request with non-ASCII in header name", 359 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cüstom: value\r\n\r\n", 360 // RFC 9110 Section 5.1: Field names must be ASCII tokens 361 }, 362 #endif 363 #ifdef SPECIAL_STRICT_PEER_CHECKING 364 { 365 .name = "Request-target as asterisk for non-OPTIONS", 366 .upload = "GET * HTTP/1.1\r\nHost: example.com\r\n\r\n", 367 // RFC 9112 Section 3.2.4: Asterisk form only valid for OPTIONS 368 }, 369 #endif 370 #ifdef MORE_PEER_VALIDATION 371 { 372 .name = "Authority-form for non-CONNECT", 373 .upload = "GET example.com:80 HTTP/1.1\r\nHost: example.com\r\n\r\n", 374 // RFC 9112 Section 3.2.3: Authority-form only valid for CONNECT 375 }, 376 #endif 377 { 378 .name = "HTTP/1.1 GET with empty Host header", 379 .upload = "GET / HTTP/1.1\r\nHost: \r\n\r\n", 380 // RFC 9112 Section 3.2: Empty Host header is invalid for this target form 381 }, 382 #ifdef MORE_PEER_VALIDATION 383 { 384 .name = "Request with quoted string in field name", 385 .upload = 386 "GET / HTTP/1.1\r\nHost: example.com\r\n\"X-Custom\": value\r\n\r\n", 387 // RFC 9110 Section 5.1: Field names must be tokens, not quoted strings 388 }, 389 #endif 390 #if 0 /* NOT A BUG */ 391 { 392 .name = "HTTP/1.1 request with HTTP/1.2 version", 393 .upload = "GET / HTTP/1.2\r\nHost: example.com\r\n\r\n", 394 /* HTTP/1.2 SHOULD be processed as 1.1 if server supports 1.1. 395 RFC 9110, Section 6.2 396 See https://www.rfc-editor.org/rfc/rfc9110.html#name-control-data */ 397 }, 398 #endif 399 { 400 .name = "HTTP/1.1 request with HTTP/0.o version", 401 .upload = "GET / HTTP/0.o\r\nHost: example.com\r\n\r\n", 402 /* Version must be "digit dot digit" 403 * RFC 9112, Section 2.3 */ 404 }, 405 { 406 .name = "HTTP/1.1 request with HTTP/0.9 version", 407 .upload = "GET / HTTP/0.9\r\nHost: example.com\r\n\r\n", 408 /* HTTP versions before HTTP/1.0 are not supported */ 409 }, 410 { 411 .name = "HTTP/1.1 request with HTTP/1.10 version", 412 .upload = "GET / HTTP/1.10\r\nHost: example.com\r\n\r\n", 413 /* Version must be "digit dot digit" 414 * RFC 9112, Section 2.3 */ 415 }, 416 { 417 .name = "HTTP/1.1 request with HTTP/1.I version", 418 .upload = "GET / HTTP/1.I\r\nHost: example.com\r\n\r\n", 419 /* Version must be "digit dot digit" 420 * RFC 9112, Section 2.3 */ 421 }, 422 { 423 .name = "HTTP/1.1 request with HTTP/1,1 version", 424 .upload = "GET / HTTP/1,1\r\nHost: example.com\r\n\r\n", 425 /* Version must be "digit dot digit" 426 * RFC 9112, Section 2.3 */ 427 }, 428 { 429 .name = "HTTP version HTTP/2.0 on HTTP/1.1 connection", 430 .upload = "GET / HTTP/2.0\r\nHost: example.com\r\n\r\n", 431 // RFC 9112 Section 2.3: HTTP/2 uses different framing, not text-based 432 }, 433 { 434 .name = "Empty method", 435 .upload = " / HTTP/1.1\r\nHost: example.com\r\n\r\n", 436 // RFC 9112 Section 3: Method is required 437 }, 438 #ifdef MORE_PEER_VALIDATION 439 { 440 .name = "Method with special character", 441 .upload = "GET/ / HTTP/1.1\r\nHost: example.com\r\n\r\n", 442 // RFC 9110 Section 9.1: Method must be a token (no '/' in method name) 443 }, 444 #endif 445 { 446 .name = "Triple Host headers", 447 .upload = 448 "GET / HTTP/1.1\r\nHost: a.com\r\nHost: b.com\r\nHost: c.com\r\n\r\n", 449 // RFC 9112 Section 3.2: Multiple Host headers forbidden 450 }, 451 { 452 .name = "Content-Length with hexadecimal", 453 .upload = 454 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0x10\r\n\r\n0123456789012345", 455 // RFC 9110 Section 8.6: Content-Length must be decimal, not hexadecimal 456 }, 457 { 458 .name = NULL, 459 } 460 }; 461 462 463 /** 464 * Tests with HTTP requests that violate MUST constraints 465 * of the HTTP specifications during the upload. 466 */ 467 static struct Test tests_must_upload[] = { 468 { 469 .name = "Chunked encoding with invalid hex", 470 .upload = 471 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nGG\r\n\r\n", 472 // RFC 9112 Section 7.1: Chunk size must be hexadecimal 473 }, 474 { 475 .name = "Chunked encoding with negative size", 476 .upload = 477 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n-5\r\n\r\n", 478 // RFC 9112 Section 7.1: Chunk size must be non-negative hex 479 }, 480 { 481 .name = "Chunked encoding missing CRLF after size", 482 .upload = 483 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5hello\r\n0\r\n\r\n", 484 // RFC 9112 Section 7.1: CRLF required after chunk-size 485 }, 486 { 487 .name = "Chunked encoding missing CRLF after data", 488 .upload = 489 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello0\r\n\r\n", 490 // RFC 9112 Section 7.1: CRLF required after chunk-data 491 }, 492 { 493 .name = "Chunked with no final chunk", 494 .upload = 495 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n", 496 // RFC 9112 Section 7.1: Last chunk (size 0) is required 497 }, 498 { 499 .name = "Chunk size with leading whitespace", 500 .upload = 501 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n 5\r\nhello\r\n0\r\n\r\n", 502 // RFC 9112 Section 7.1: No whitespace before chunk-size 503 }, 504 { 505 .name = "Chunk size exceeding data provided", 506 .upload = 507 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nA\r\nhello\r\n0\r\n\r\n", 508 // RFC 9112 Section 7.1: Chunk-size must match chunk-data length (10 bytes expected, 5 provided) 509 }, 510 { 511 .name = NULL, 512 } 513 }; 514 515 516 /** 517 * Tests with HTTP requests that violate SHOULD constraints 518 * of the HTTP specifications. 519 * 520 * For example, for chunked encoding, this level (and more restrictive 521 * ones) forbids whitespace in chunk extensions. For cookie parsing, 522 * this level (and more restrictive ones) rejects the entire cookie if 523 * even a single value within it is incorrectly encoded. 524 */ 525 static struct Test tests_should[] = { 526 { 527 .name = "Obsolete line folding in header", 528 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n continuation\r\n\r\n", 529 // RFC 9112 Section 5.2: Line folding is obsolete, recipients SHOULD reject or replace with SP 530 }, 531 #ifdef NOT_A_BUG 532 { 533 .name = "Multiple spaces after colon in header", 534 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", 535 // RFC 9110 Section 5.6.3: Senders SHOULD NOT generate optional whitespace except as SP 536 /* RFC 9110 Section 5.6.3: "OWS and RWS have the same semantics as a single SP." 537 * RFC 9112 Section 5.1: "A field line value might be preceded and/or followed by optional whitespace (OWS)" 538 * The server must parse this value as a correct value. */ 539 }, 540 #endif 541 #ifdef NOT_A_BUG 542 { 543 .name = "Trailing whitespace in header value", 544 .upload = "GET / HTTP/1.1\r\nHost: example.com \r\n\r\n", 545 // RFC 9110 Section 5.3: Trailing whitespace should be stripped 546 /* RFC 9112 Section 5.1: "OWS occurring before the first non-whitespace octet of the field line value, 547 or after the last non-whitespace octet of the field line value, is excluded 548 by parsers when extracting the field line value from a field line." 549 * This is a valid request. */ 550 }, 551 #endif 552 #ifdef NOT_A_BUG 553 { 554 .name = "Leading whitespace in header value", 555 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", 556 // RFC 9110 Section 5.3: Leading whitespace should be stripped 557 /* RFC 9112 Section 5.1: "OWS occurring before the first non-whitespace octet of the field line value, 558 or after the last non-whitespace octet of the field line value, is excluded 559 by parsers when extracting the field line value from a field line." 560 * This is a valid request. */ 561 }, 562 #endif 563 #ifdef NOT_A_BUG 564 { 565 .name = "Chunk extension with whitespace", 566 .upload = 567 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5 ; ext=val\r\nhello\r\n0\r\n\r\n", 568 // RFC 9112 Section 7.1.1: Whitespace in chunk extensions should be minimal 569 /* This is a valid request. */ 570 }, 571 #endif 572 #ifdef NOT_A_BUG 573 { 574 .name = "Chunked with uppercase hex digits", 575 .upload = 576 "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n", 577 // RFC 9112 Section 7.1: Senders SHOULD use lowercase for hex digits (though uppercase is valid) 578 /* This is a valid request. It must be parsed. */ 579 }, 580 #endif 581 #ifdef NOT_A_BUG 582 { 583 .name = "Empty header field value", 584 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Empty:\r\n\r\n", 585 // RFC 9110 Section 5.3: Empty field values are valid but some implementations may reject 586 /* This is a valid request. It must be parsed. */ 587 }, 588 #endif 589 #ifdef NOT_A_BUG 590 { 591 .name = "Header field name with uppercase and lowercase", 592 .upload = "GET / HTTP/1.1\r\nHoSt: example.com\r\n\r\n", 593 // RFC 9110 Section 5.1: Field names are case-insensitive, but conventional capitalization SHOULD be used 594 /* This is a valid request. It must be parsed. */ 595 }, 596 #endif 597 { 598 .name = "Excessive whitespace in request line", 599 .upload = "GET / HTTP/1.1 \r\nHost: example.com\r\n\r\n", 600 // RFC 9112 Section 3: Trailing whitespace in request-line should not be present 601 }, 602 #ifdef NOT_A_BUG 603 { 604 .name = "Content-Length with leading zeros", 605 .upload = 606 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0005\r\n\r\nhello", 607 // RFC 9110 Section 8.6: Leading zeros should not be sent 608 /* This is a valid request. It must be parsed. */ 609 }, 610 #endif 611 #ifdef NOT_A_BUG 612 { 613 .name = "Cookie header with invalid encoding in value", 614 .upload = 615 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: name=val ue\r\n\r\n", 616 // RFC 6265 Section 4.2: Cookie values should be properly encoded, spaces require encoding 617 /* Rejected completely on stricter level. On default level valid part "name=val" is used. */ 618 }, 619 #endif 620 { 621 .name = NULL, 622 } 623 }; 624 625 626 static struct Test *current; 627 628 629 /** 630 * Our port. 631 */ 632 static uint16_t port; 633 634 /** 635 * Set to true if a test failed. 636 */ 637 static volatile bool failed; 638 639 640 /** 641 * Function to process data uploaded by a client. 642 * 643 * Given that we ONLY generate incorrect/malformed upload requests, 644 * this function should never be called. 645 * 646 * @param upload_cls the argument given together with the function 647 * pointer when the handler was registered with MHD 648 * @param request the request is being processed 649 * @param content_data_size the size of the @a content_data, 650 * zero when all data have been processed 651 * @param[in] content_data the uploaded content data, 652 * may be modified in the callback, 653 * valid only until return from the callback, 654 * NULL when all data have been processed 655 * @return action specifying how to proceed: 656 * #MHD_upload_action_continue() to continue upload (for incremental 657 * upload processing only), 658 * #MHD_upload_action_suspend() to stop reading the upload until 659 * the request is resumed, 660 * #MHD_upload_action_abort_request() to close the socket, 661 * or a response to discard the rest of the upload and transmit 662 * the response 663 * @ingroup action 664 */ 665 static const struct MHD_UploadAction * 666 uc_fail (void *upload_cls, 667 struct MHD_Request *request, 668 size_t content_data_size, 669 void *content_data) 670 { 671 fprintf (stderr, 672 "Test `%s' failed\n", 673 current->name); 674 failed = true; 675 return NULL; 676 } 677 678 679 /** 680 * A client has requested the given url using the given method 681 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 682 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 683 * If @a upload_size is not zero and response action is provided by this 684 * callback, then upload will be discarded and the stream (the connection for 685 * HTTP/1.1) will be closed after sending the response. 686 * 687 * This function is expected to be called, but when we try to 688 * process the upload it should always fail on the MHD side. 689 * 690 * @param cls argument given together with the function 691 * pointer when the handler was registered with MHD 692 * @param request the request object 693 * @param path the requested uri (without arguments after "?") 694 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 695 * #MHD_HTTP_METHOD_PUT, etc.) 696 * @param upload_size the size of the message upload content payload, 697 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 698 * final chunk has not been processed yet) 699 * @return action how to proceed, NULL 700 * if the request must be aborted due to a serious 701 * error while handling the request (implies closure 702 * of underling data stream, for HTTP/1.1 it means 703 * socket closure). 704 */ 705 static const struct MHD_Action * 706 server_upload_req_cb (void *cls, 707 struct MHD_Request *MHD_RESTRICT request, 708 const struct MHD_String *MHD_RESTRICT path, 709 enum MHD_HTTP_Method method, 710 uint_fast64_t upload_size) 711 { 712 return MHD_action_process_upload (request, 713 1024 * 1024, 714 &uc_fail, 715 NULL, 716 &uc_fail, 717 NULL); 718 } 719 720 721 /** 722 * A client has requested the given url using the given method 723 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 724 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 725 * If @a upload_size is not zero and response action is provided by this 726 * callback, then upload will be discarded and the stream (the connection for 727 * HTTP/1.1) will be closed after sending the response. 728 * 729 * Given that we ONLY generate incorrect/malformed requests, 730 * this function should never be called. 731 * 732 * @param cls argument given together with the function 733 * pointer when the handler was registered with MHD 734 * @param request the request object 735 * @param path the requested uri (without arguments after "?") 736 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 737 * #MHD_HTTP_METHOD_PUT, etc.) 738 * @param upload_size the size of the message upload content payload, 739 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 740 * final chunk has not been processed yet) 741 * @return action how to proceed, NULL 742 * if the request must be aborted due to a serious 743 * error while handling the request (implies closure 744 * of underling data stream, for HTTP/1.1 it means 745 * socket closure). 746 */ 747 static const struct MHD_Action * 748 server_req_cb (void *cls, 749 struct MHD_Request *MHD_RESTRICT request, 750 const struct MHD_String *MHD_RESTRICT path, 751 enum MHD_HTTP_Method method, 752 uint_fast64_t upload_size) 753 { 754 fprintf (stderr, 755 "Test `%s' failed\n", 756 current->name); 757 failed = true; 758 return NULL; 759 } 760 761 762 /** 763 * Helper function to deal with partial writes. 764 * Fails hard (calls exit() on failures)! 765 * 766 * @param fd where to write to 767 * @param buf what to write 768 * @param buf_size number of bytes in @a buf 769 */ 770 static void 771 write_all (int fd, 772 const void *buf, 773 size_t buf_size) 774 { 775 const char *cbuf = buf; 776 size_t off; 777 778 off = 0; 779 while (off < buf_size) 780 { 781 ssize_t ret; 782 783 ret = write (fd, 784 &cbuf[off], 785 buf_size - off); 786 if (ret <= 0) 787 { 788 fprintf (stderr, 789 "Writing %u bytes to %d failed: %s\n", 790 (unsigned int)(buf_size - off), 791 fd, 792 strerror (errno)); 793 exit (1); 794 } 795 off += ret; 796 } 797 } 798 799 800 static int 801 run_test () 802 { 803 int s; 804 struct sockaddr_in sa = { 805 .sin_family = AF_INET, 806 .sin_port = htons (port), 807 }; 808 char dummy; 809 810 s = socket (AF_INET, SOCK_STREAM, 0); 811 if (-1 == s) 812 { 813 fprintf (stderr, 814 "socket() failed: %s\n", 815 strerror (errno)); 816 return 1; 817 } 818 inet_pton (AF_INET, 819 "127.0.0.1", 820 &sa.sin_addr); 821 if (0 != connect (s, 822 (struct sockaddr *)&sa, 823 sizeof (sa))) 824 { 825 fprintf (stderr, 826 "bind() failed: %s\n", 827 strerror (errno)); 828 close (s); 829 return 1; 830 } 831 write_all (s, 832 current->upload, 833 strlen (current->upload)); 834 shutdown (s, 835 SHUT_WR); 836 if (((ssize_t)sizeof (dummy)) != 837 read (s, 838 &dummy, 839 sizeof (dummy))) 840 { 841 #if LOG 842 fprintf (stderr, 843 "Server closed connection\n"); 844 #endif 845 } 846 close (s); 847 if (failed) 848 return 1; 849 return 0; 850 } 851 852 853 static int 854 run_tests (struct Test *tests) 855 { 856 for (unsigned int i = 0; 857 NULL != tests[i].name; 858 i++) 859 { 860 current = &tests[i]; 861 #if LOG || 1 862 fprintf (stderr, 863 "Running test `%s'\n", 864 current->name); 865 #endif 866 if (0 != run_test ()) 867 return 1; 868 } 869 return 0; 870 } 871 872 873 static int 874 run (MHD_RequestCallback cb, 875 struct Test *tests, 876 enum MHD_ProtocolStrictLevel psl) 877 { 878 struct MHD_Daemon *d; 879 880 d = MHD_daemon_create (cb, 881 NULL); 882 if (MHD_SC_OK != 883 MHD_DAEMON_SET_OPTIONS ( 884 d, 885 MHD_D_OPTION_WM_WORKER_THREADS (2), 886 #if !LOG 887 MHD_D_OPTION_LOG_CALLBACK (NULL, 888 NULL), 889 #endif 890 MHD_D_OPTION_PROTOCOL_STRICT_LEVEL (psl, 891 MHD_USL_PRECISE), 892 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1000), 893 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 894 0))) 895 { 896 fprintf (stderr, 897 "Failed to configure daemon!"); 898 return 1; 899 } 900 901 { 902 enum MHD_StatusCode sc; 903 904 sc = MHD_daemon_start (d); 905 if (MHD_SC_OK != sc) 906 { 907 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 908 fprintf (stderr, 909 "Failed to start server: %s\n", 910 MHD_status_code_to_string_lazy (sc)); 911 #else 912 fprintf (stderr, 913 "Failed to start server: %u\n", 914 (unsigned int)sc); 915 #endif 916 MHD_daemon_destroy (d); 917 return 1; 918 } 919 } 920 921 { 922 union MHD_DaemonInfoFixedData info; 923 enum MHD_StatusCode sc; 924 925 sc = MHD_daemon_get_info_fixed ( 926 d, 927 MHD_DAEMON_INFO_FIXED_BIND_PORT, 928 &info); 929 if (MHD_SC_OK != sc) 930 { 931 fprintf (stderr, 932 "Failed to determine our port: %u\n", 933 (unsigned int)sc); 934 MHD_daemon_destroy (d); 935 return 1; 936 } 937 port = info.v_bind_port_uint16; 938 } 939 940 { 941 int result; 942 943 result = run_tests (tests); 944 MHD_daemon_destroy (d); 945 return result; 946 } 947 } 948 949 950 int 951 main (void) 952 { 953 if (0 != 954 run (&server_req_cb, 955 tests_must, 956 MHD_PSL_STRICT)) 957 return 1; 958 if (0 != 959 run (&server_upload_req_cb, 960 tests_must_upload, 961 MHD_PSL_STRICT)) 962 return 1; 963 if (0 != 964 run (&server_req_cb, 965 tests_should, 966 MHD_PSL_VERY_STRICT)) 967 return 1; 968 return 0; 969 }