test_raw.c (31393B)
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_raw.c 41 * @brief tests streams against server 42 * @author Christian Grothoff 43 */ 44 #include <stdio.h> 45 #include <stdbool.h> 46 #include <errno.h> 47 #include <string.h> 48 #include <stdlib.h> 49 #include <unistd.h> 50 #include <arpa/inet.h> 51 #include <netinet/ip.h> 52 #include "microhttpd2.h" 53 54 55 /** 56 * Defines a test. 57 */ 58 struct Test 59 { 60 /** 61 * Human-readable name of the test. NULL to end test array. 62 */ 63 const char *name; 64 65 /** 66 * Request to send to the server. 67 */ 68 const char *upload; 69 70 /** 71 * Expected HTTP method. 72 */ 73 enum MHD_HTTP_Method expect_method; 74 75 /** 76 * Expected path. 77 */ 78 const char *expect_path; 79 80 /** 81 * Expected upload size. 82 */ 83 uint_fast64_t expect_upload_size; 84 85 /** 86 * Special string indicating what the MHD parser should give us. 87 * Can be used to encode expected HTTP headers using 88 * "H-$KEY:$VALUE" or HTTP cookies using "C-$KEY:$VALUE". 89 * Multiple entries are separated via "\n". 90 */ 91 const char *expect_parser; 92 }; 93 94 95 static struct Test tests[] = { 96 { 97 .name = "Basic GET", 98 .upload = "GET / HTTP/1.0\r\n\r\n", 99 .expect_method = MHD_HTTP_METHOD_GET, 100 .expect_path = "/", 101 }, 102 { 103 .name = "Basic HTTP/1.1 GET", 104 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n", 105 .expect_method = MHD_HTTP_METHOD_GET, 106 .expect_path = "/", 107 .expect_parser = "H-Host:example.com", 108 }, 109 { 110 .name = "Multi-header HTTP/1.1 GET with query parameters", 111 .upload = "GET /?k=v&a HTTP/1.1\r\nHost: example.com\r\nKey: value\r\n\r\n", 112 .expect_method = MHD_HTTP_METHOD_GET, 113 .expect_path = "/", 114 .expect_parser = "H-Host:example.com\nH-Key:value\nQ-k:v\nQ-a", 115 }, 116 { 117 .name = "Empty header value", 118 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Empty:\r\n\r\n", 119 .expect_method = MHD_HTTP_METHOD_GET, 120 .expect_path = "/", 121 .expect_parser = "H-Host:example.com\nH-X-Empty:", 122 }, 123 { 124 .name = "Header with leading/trailing whitespace", 125 .upload = 126 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Space: value \r\n\r\n", 127 .expect_method = MHD_HTTP_METHOD_GET, 128 .expect_path = "/", 129 .expect_parser = "H-Host:example.com\nH-X-Space:value", 130 }, 131 { 132 .name = "Multiple headers with same name", 133 .upload = 134 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Dup: first\r\nX-Dup: second\r\n\r\n", 135 .expect_method = MHD_HTTP_METHOD_GET, 136 .expect_path = "/", 137 .expect_parser = "H-Host:example.com\nH-X-Dup:first", 138 }, 139 { 140 .name = "Case insensitive header names", 141 .upload = 142 "GET / HTTP/1.1\r\nhost: example.com\r\nContent-TYPE: text/plain\r\n\r\n", 143 .expect_method = MHD_HTTP_METHOD_GET, 144 .expect_path = "/", 145 .expect_parser = "H-Host:example.com\nH-Content-Type:text/plain", 146 }, 147 { 148 .name = "Query parameter with empty value", 149 .upload = "GET /?key= HTTP/1.1\r\nHost: example.com\r\n\r\n", 150 .expect_method = MHD_HTTP_METHOD_GET, 151 .expect_path = "/", 152 .expect_parser = "H-Host:example.com\nQ-key:", 153 }, 154 { 155 .name = "Query parameter without value", 156 .upload = "GET /?flag HTTP/1.1\r\nHost: example.com\r\n\r\n", 157 .expect_method = MHD_HTTP_METHOD_GET, 158 .expect_path = "/", 159 .expect_parser = "H-Host:example.com\nQ-flag", 160 }, 161 { 162 .name = "Multiple query parameters", 163 .upload = "GET /?a=1&b=2&c=3 HTTP/1.1\r\nHost: example.com\r\n\r\n", 164 .expect_method = MHD_HTTP_METHOD_GET, 165 .expect_path = "/", 166 .expect_parser = "H-Host:example.com\nQ-a:1\nQ-b:2\nQ-c:3", 167 }, 168 { 169 .name = "URL encoded query parameter", 170 .upload = 171 "GET /?name=hello%20world&special=%21%40%23 HTTP/1.1\r\nHost: example.com\r\n\r\n", 172 .expect_method = MHD_HTTP_METHOD_GET, 173 .expect_path = "/", 174 .expect_parser = "H-Host:example.com\nQ-name:hello world\nQ-special:!@#", 175 }, 176 { 177 .name = "Simple cookie", 178 .upload = 179 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: session=abc123\r\n\r\n", 180 .expect_method = MHD_HTTP_METHOD_GET, 181 .expect_path = "/", 182 .expect_parser = "H-Host:example.com\nC-session:abc123", 183 }, 184 { 185 .name = "Multiple cookies in one header", 186 .upload = 187 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: a=1; b=2; c=3\r\n\r\n", 188 .expect_method = MHD_HTTP_METHOD_GET, 189 .expect_path = "/", 190 .expect_parser = "H-Host:example.com\nC-a:1\nC-b:2\nC-c:3", 191 }, 192 #if COMPATIBILITY_QUESTION 193 { 194 .name = "Cookie with spaces", 195 .upload = 196 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: key = value\r\n\r\n", 197 .expect_method = MHD_HTTP_METHOD_GET, 198 .expect_path = "/", 199 .expect_parser = "H-Host:example.com\nC-key:value", 200 }, 201 #endif 202 { 203 .name = "POST with Content-Length", 204 .upload = 205 "POST /submit HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\n\r\nhello", 206 .expect_method = MHD_HTTP_METHOD_POST, 207 .expect_path = "/submit", 208 .expect_upload_size = 5, 209 .expect_parser = "H-Host:example.com\nH-Content-Length:5", 210 }, 211 #if VERY_BAD_BUG 212 { 213 .name = "Path with special characters", 214 .upload = 215 "GET /path/to/resource%20file.html HTTP/1.1\r\nHost: example.com\r\n\r\n", 216 .expect_method = MHD_HTTP_METHOD_GET, 217 .expect_path = "/path/to/resource file.html", 218 .expect_parser = "H-Host:example.com", 219 }, 220 #endif 221 { 222 .name = "Long header value", 223 .upload = 224 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Long: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n", 225 .expect_method = MHD_HTTP_METHOD_GET, 226 .expect_path = "/", 227 .expect_parser = 228 "H-Host:example.com\nH-X-Long:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 229 }, 230 { 231 .name = "Query string with equals sign in value", 232 .upload = "GET /?expr=a=b HTTP/1.1\r\nHost: example.com\r\n\r\n", 233 .expect_method = MHD_HTTP_METHOD_GET, 234 .expect_path = "/", 235 .expect_parser = "H-Host:example.com\nQ-expr:a=b", 236 }, 237 { 238 .name = "PATCH request", 239 .upload = 240 "PATCH /resource HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n", 241 .expect_method = MHD_HTTP_METHOD_OTHER, 242 .expect_path = "/resource", 243 .expect_upload_size = 0, 244 .expect_parser = "H-Host:example.com\nH-Content-Length:0", 245 }, 246 { 247 .name = "Mixed query parameters with and without values", 248 .upload = 249 "GET /?debug&level=5&verbose HTTP/1.1\r\nHost: example.com\r\n\r\n", 250 .expect_method = MHD_HTTP_METHOD_GET, 251 .expect_path = "/", 252 .expect_parser = "H-Host:example.com\nQ-debug\nQ-level:5\nQ-verbose", 253 }, 254 #if PARSER_BUG 255 { 256 .name = "Continuation header (folded)", 257 .upload = 258 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Multi: line one\r\n line two\r\n\r\n", 259 .expect_method = MHD_HTTP_METHOD_GET, 260 .expect_path = "/", 261 .expect_parser = "H-Host:example.com\nH-X-Multi:line one line two", 262 }, 263 #endif 264 { 265 .name = "Empty query string", 266 .upload = "GET /? HTTP/1.1\r\nHost: example.com\r\n\r\n", 267 .expect_method = MHD_HTTP_METHOD_GET, 268 .expect_path = "/", 269 .expect_parser = "H-Host:example.com", 270 }, 271 #if UNDERSPECIFIED_LIKELY_BAD 272 { 273 .name = "Fragment in URI (should probably be stripped!)", 274 .upload = "GET /page#section HTTP/1.1\r\nHost: example.com\r\n\r\n", 275 .expect_method = MHD_HTTP_METHOD_GET, 276 .expect_path = "/page", 277 .expect_parser = "H-Host:example.com", 278 }, 279 #endif 280 { 281 .name = "PUT request with headers", 282 .upload = 283 "PUT /resource HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: 0\r\n\r\n", 284 .expect_method = MHD_HTTP_METHOD_PUT, 285 .expect_path = "/resource", 286 .expect_upload_size = 0, 287 .expect_parser = 288 "H-Host:example.com\nH-Content-Type:application/json\nH-Content-Length:0", 289 }, 290 { 291 .name = "DELETE request", 292 .upload = "DELETE /resource/123 HTTP/1.1\r\nHost: example.com\r\n\r\n", 293 .expect_method = MHD_HTTP_METHOD_DELETE, 294 .expect_path = "/resource/123", 295 .expect_parser = "H-Host:example.com", 296 }, 297 298 { 299 .name = "Header with colon in value", 300 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Time: 12:34:56\r\n\r\n", 301 .expect_method = MHD_HTTP_METHOD_GET, 302 .expect_path = "/", 303 .expect_parser = "H-Host:example.com\nH-X-Time:12:34:56", 304 }, 305 { 306 .name = "Cookie with empty value", 307 .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: empty=\r\n\r\n", 308 .expect_method = MHD_HTTP_METHOD_GET, 309 .expect_path = "/", 310 .expect_parser = "H-Host:example.com\nC-empty:", 311 }, 312 { 313 .name = "Query parameter with plus sign encoding", 314 .upload = "GET /?text=hello+world HTTP/1.1\r\nHost: example.com\r\n\r\n", 315 .expect_method = MHD_HTTP_METHOD_GET, 316 .expect_path = "/", 317 .expect_parser = "H-Host:example.com\nQ-text:hello world", 318 }, 319 { 320 .name = "Multiple Cookie headers", 321 .upload = 322 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: a=1\r\nCookie: b=2\r\n\r\n", 323 .expect_method = MHD_HTTP_METHOD_GET, 324 .expect_path = "/", 325 .expect_parser = "H-Host:example.com\nC-a:1\nC-b:2", 326 }, 327 { 328 .name = "HEAD request", 329 .upload = "HEAD /resource HTTP/1.1\r\nHost: example.com\r\n\r\n", 330 .expect_method = MHD_HTTP_METHOD_HEAD, 331 .expect_path = "/resource", 332 .expect_parser = "H-Host:example.com", 333 }, 334 { 335 .name = "OPTIONS request", 336 .upload = "OPTIONS * HTTP/1.1\r\nHost: example.com\r\n\r\n", 337 .expect_method = MHD_HTTP_METHOD_OPTIONS, 338 .expect_path = "*", 339 .expect_parser = "H-Host:example.com", 340 }, 341 { 342 .name = "Query with ampersand in value", 343 .upload = "GET /?code=a%26b HTTP/1.1\r\nHost: example.com\r\n\r\n", 344 .expect_method = MHD_HTTP_METHOD_GET, 345 .expect_path = "/", 346 .expect_parser = "H-Host:example.com\nQ-code:a&b", 347 }, 348 { 349 .name = "Query with percent sign in value", 350 .upload = "GET /?percent=100%25 HTTP/1.1\r\nHost: example.com\r\n\r\n", 351 .expect_method = MHD_HTTP_METHOD_GET, 352 .expect_path = "/", 353 .expect_parser = "H-Host:example.com\nQ-percent:100%", 354 }, 355 { 356 .name = "Root path with trailing slash", 357 .upload = "GET // HTTP/1.1\r\nHost: example.com\r\n\r\n", 358 .expect_method = MHD_HTTP_METHOD_GET, 359 .expect_path = "//", 360 .expect_parser = "H-Host:example.com", 361 }, 362 { 363 .name = "Deep path nesting", 364 .upload = "GET /a/b/c/d/e/f/g HTTP/1.1\r\nHost: example.com\r\n\r\n", 365 .expect_method = MHD_HTTP_METHOD_GET, 366 .expect_path = "/a/b/c/d/e/f/g", 367 .expect_parser = "H-Host:example.com", 368 }, 369 { 370 .name = "Authorization header", 371 .upload = 372 "GET / HTTP/1.1\r\nHost: example.com\r\nAuthorization: Bearer token123\r\n\r\n", 373 .expect_method = MHD_HTTP_METHOD_GET, 374 .expect_path = "/", 375 .expect_parser = "H-Host:example.com\nH-Authorization:Bearer token123", 376 }, 377 { 378 .name = "User-Agent header", 379 .upload = 380 "GET / HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Mozilla/5.0\r\n\r\n", 381 .expect_method = MHD_HTTP_METHOD_GET, 382 .expect_path = "/", 383 .expect_parser = "H-Host:example.com\nH-User-Agent:Mozilla/5.0", 384 }, 385 { 386 .name = "Accept header with multiple values", 387 .upload = 388 "GET / HTTP/1.1\r\nHost: example.com\r\nAccept: text/html, application/json\r\n\r\n", 389 .expect_method = MHD_HTTP_METHOD_GET, 390 .expect_path = "/", 391 .expect_parser = "H-Host:example.com\nH-Accept:text/html, application/json", 392 }, 393 { 394 .name = "Connection keep-alive", 395 .upload = 396 "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\n\r\n", 397 .expect_method = MHD_HTTP_METHOD_GET, 398 .expect_path = "/", 399 .expect_parser = "H-Host:example.com\nH-Connection:keep-alive", 400 }, 401 { 402 .name = "Query with duplicate parameters", 403 .upload = "GET /?id=1&id=2&id=3 HTTP/1.1\r\nHost: example.com\r\n\r\n", 404 .expect_method = MHD_HTTP_METHOD_GET, 405 .expect_path = "/", 406 .expect_parser = "H-Host:example.com\nQ-id:1", 407 }, 408 { 409 .name = "Cookie with quoted value", 410 .upload = 411 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: session=\"abc123\"\r\n\r\n", 412 .expect_method = MHD_HTTP_METHOD_GET, 413 .expect_path = "/", 414 .expect_parser = "H-Host:example.com\nC-session:abc123", 415 }, 416 { 417 .name = "Query with UTF-8 encoding", 418 .upload = "GET /?name=%E2%9C%93 HTTP/1.1\r\nHost: example.com\r\n\r\n", 419 .expect_method = MHD_HTTP_METHOD_GET, 420 .expect_path = "/", 421 .expect_parser = "H-Host:example.com\nQ-name:✓", 422 }, 423 { 424 .name = "Referer header", 425 .upload = 426 "GET /page HTTP/1.1\r\nHost: example.com\r\nReferer: https://google.com\r\n\r\n", 427 .expect_method = MHD_HTTP_METHOD_GET, 428 .expect_path = "/page", 429 .expect_parser = "H-Host:example.com\nH-Referer:https://google.com", 430 }, 431 { 432 .name = "Content-Type with charset", 433 .upload = 434 "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\n\r\n", 435 .expect_method = MHD_HTTP_METHOD_POST, 436 .expect_path = "/", 437 .expect_upload_size = 0, 438 .expect_parser = 439 "H-Host:example.com\nH-Content-Type:text/html; charset=utf-8\nH-Content-Length:0", 440 }, 441 { 442 .name = "Cache-Control header", 443 .upload = 444 "GET / HTTP/1.1\r\nHost: example.com\r\nCache-Control: no-cache\r\n\r\n", 445 .expect_method = MHD_HTTP_METHOD_GET, 446 .expect_path = "/", 447 .expect_parser = "H-Host:example.com\nH-Cache-Control:no-cache", 448 }, 449 { 450 .name = "If-None-Match header", 451 .upload = 452 "GET / HTTP/1.1\r\nHost: example.com\r\nIf-None-Match: \"etag123\"\r\n\r\n", 453 .expect_method = MHD_HTTP_METHOD_GET, 454 .expect_path = "/", 455 .expect_parser = "H-Host:example.com\nH-If-None-Match:\"etag123\"", 456 }, 457 { 458 .name = "Range header", 459 .upload = 460 "GET /file HTTP/1.1\r\nHost: example.com\r\nRange: bytes=0-1023\r\n\r\n", 461 .expect_method = MHD_HTTP_METHOD_GET, 462 .expect_path = "/file", 463 .expect_parser = "H-Host:example.com\nH-Range:bytes=0-1023", 464 }, 465 { 466 .name = "X-Forwarded-For header", 467 .upload = 468 "GET / HTTP/1.1\r\nHost: example.com\r\nX-Forwarded-For: 192.168.1.1\r\n\r\n", 469 .expect_method = MHD_HTTP_METHOD_GET, 470 .expect_path = "/", 471 .expect_parser = "H-Host:example.com\nH-X-Forwarded-For:192.168.1.1", 472 }, 473 { 474 .name = "Query with empty parameter name", 475 .upload = "GET /?=value HTTP/1.1\r\nHost: example.com\r\n\r\n", 476 .expect_method = MHD_HTTP_METHOD_GET, 477 .expect_path = "/", 478 .expect_parser = "H-Host:example.com\nQ-:value", 479 }, 480 #if QUESTIONABLE_TEST 481 { 482 .name = "Absolute URI in request line", 483 .upload = "GET http://example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n", 484 .expect_method = MHD_HTTP_METHOD_GET, 485 .expect_path = "/", 486 .expect_parser = "H-Host:example.com", 487 }, 488 #endif 489 { 490 .name = "Query with semicolon separator", 491 .upload = "GET /?a=1;b=2 HTTP/1.1\r\nHost: example.com\r\n\r\n", 492 .expect_method = MHD_HTTP_METHOD_GET, 493 .expect_path = "/", 494 .expect_parser = "H-Host:example.com\nQ-a:1;b=2", 495 }, 496 { 497 .name = "Cookie with special characters", 498 .upload = 499 "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: data=value_with-special.chars\r\n\r\n", 500 .expect_method = MHD_HTTP_METHOD_GET, 501 .expect_path = "/", 502 .expect_parser = "H-Host:example.com\nC-data:value_with-special.chars", 503 }, 504 { 505 .name = "Minimal HTTP/1.0 request with path only", 506 .upload = "GET /path HTTP/1.0\r\n\r\n", 507 .expect_method = MHD_HTTP_METHOD_GET, 508 .expect_path = "/path", 509 }, 510 { 511 .name = "POST with form data Content-Type", 512 .upload = 513 "POST /form HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: 0\r\n\r\n", 514 .expect_method = MHD_HTTP_METHOD_POST, 515 .expect_path = "/form", 516 .expect_upload_size = 0, 517 .expect_parser = 518 "H-Host:example.com\nH-Content-Type:application/x-www-form-urlencoded\nH-Content-Length:0", 519 }, 520 { 521 .name = "Query with trailing ampersand", 522 .upload = "GET /?a=1& HTTP/1.1\r\nHost: example.com\r\n\r\n", 523 .expect_method = MHD_HTTP_METHOD_GET, 524 .expect_path = "/", 525 .expect_parser = "H-Host:example.com\nQ-a:1", 526 }, 527 528 { 529 .name = NULL, 530 } 531 }; 532 533 534 static struct Test *current; 535 536 537 /** 538 * Our port. 539 */ 540 static uint16_t port; 541 542 543 static bool 544 check_headers (struct MHD_Request *MHD_RESTRICT request, 545 const char *spec) 546 { 547 bool ok = true; 548 char *hdr = strdup (spec); 549 char *tok; 550 char key[strlen (spec)]; 551 char value[strlen (spec)]; 552 553 for (tok = strtok (hdr, 554 "\n"); 555 NULL != tok; 556 tok = strtok (NULL, 557 "\n")) 558 { 559 char dummy; 560 561 if (2 == 562 sscanf (tok, 563 "H-%[^:]:%[^\n]s", 564 key, 565 value)) 566 { 567 struct MHD_StringNullable have; 568 569 if (! MHD_request_get_value (request, 570 MHD_VK_HEADER, 571 key, 572 &have)) 573 { 574 fprintf (stderr, 575 "Expected header `%s' missing\n", 576 key); 577 ok = false; 578 } 579 else if ( (NULL == have.cstr) || 580 (0 != strcmp (have.cstr, 581 value)) ) 582 { 583 fprintf (stderr, 584 "Wrong header value `%s' under key `%s', expected `%s'\n", 585 have.cstr, 586 key, 587 value); 588 ok = false; 589 } 590 } 591 else if (2 == 592 sscanf (tok, 593 "C-%[^:]:%[^\n]", 594 key, 595 value)) 596 { 597 struct MHD_StringNullable have; 598 599 if (! MHD_request_get_value (request, 600 MHD_VK_COOKIE, 601 key, 602 &have)) 603 { 604 fprintf (stderr, 605 "Expected cookie `%s' missing\n", 606 key); 607 ok = false; 608 } 609 else if ( (NULL == have.cstr) || 610 (0 != strcmp (have.cstr, 611 value)) ) 612 { 613 fprintf (stderr, 614 "Wrong cookie value `%s' under key `%s', expected `%s'\n", 615 have.cstr, 616 key, 617 value); 618 ok = false; 619 } 620 } 621 else if (2 == 622 sscanf (tok, 623 "Q-%[^:]:%[^\n]", 624 key, 625 value)) 626 { 627 struct MHD_StringNullable have; 628 629 if (! MHD_request_get_value (request, 630 MHD_VK_URI_QUERY_PARAM, 631 key, 632 &have)) 633 { 634 fprintf (stderr, 635 "Expected URI query parameter `%s' missing\n", 636 key); 637 ok = false; 638 } 639 else if ( (NULL == have.cstr) || 640 (0 != strcmp (have.cstr, 641 value)) ) 642 { 643 fprintf (stderr, 644 "Wrong URI query parameter value `%s' under key `%s', expected `%s'\n", 645 have.cstr, 646 key, 647 value); 648 ok = false; 649 } 650 } 651 else if (1 == 652 sscanf (tok, 653 "H-%[^:]:%[^\n]", 654 key, 655 value)) 656 { 657 struct MHD_StringNullable have; 658 659 if (! MHD_request_get_value (request, 660 MHD_VK_HEADER, 661 key, 662 &have)) 663 { 664 fprintf (stderr, 665 "Expected header `%s' missing\n", 666 key); 667 ok = false; 668 } /* HTTP header can never be "NULL", only empty string */ 669 else if ( (NULL == have.cstr) || 670 (0 != strcmp (have.cstr, 671 "")) ) 672 { 673 fprintf (stderr, 674 "Unexpected non-empty header value `%s' under key `%s'\n", 675 have.cstr, 676 key); 677 ok = false; 678 } 679 } 680 else if (2 == 681 sscanf (tok, 682 "C-%[^:]%c%[^\n]", 683 key, 684 &dummy, 685 value)) 686 { 687 struct MHD_StringNullable have; 688 689 if (! MHD_request_get_value (request, 690 MHD_VK_COOKIE, 691 key, 692 &have)) 693 { 694 fprintf (stderr, 695 "Expected cookie `%s' missing\n", 696 key); 697 ok = false; 698 } 699 else if ( (NULL == have.cstr) || 700 (0 != strcmp (have.cstr, 701 "")) ) 702 { 703 fprintf (stderr, 704 "Unexpected non-empty cookie value `%s' under key `%s'\n", 705 have.cstr, 706 key); 707 ok = false; 708 } 709 } 710 else if (1 == 711 sscanf (tok, 712 "C-%[^:]:%[^\n]", 713 key, 714 value)) 715 { 716 struct MHD_StringNullable have; 717 718 if (! MHD_request_get_value (request, 719 MHD_VK_COOKIE, 720 key, 721 &have)) 722 { 723 fprintf (stderr, 724 "Expected cookie `%s' missing\n", 725 key); 726 ok = false; 727 } 728 else if (NULL != have.cstr) 729 { 730 fprintf (stderr, 731 "Unexpected non-NULL cookie value `%s' under key `%s'\n", 732 have.cstr, 733 key); 734 ok = false; 735 } 736 } 737 else if (2 == 738 sscanf (tok, 739 "Q-%[^:]%c%[^\n]", 740 key, 741 &dummy, 742 value)) 743 { 744 struct MHD_StringNullable have; 745 746 if (! MHD_request_get_value (request, 747 MHD_VK_URI_QUERY_PARAM, 748 key, 749 &have)) 750 { 751 fprintf (stderr, 752 "Expected URI query parameter `%s' missing\n", 753 key); 754 ok = false; 755 } 756 else if ( (NULL == have.cstr) || 757 (0 != strcmp (have.cstr, 758 "")) ) 759 { 760 fprintf (stderr, 761 "Unexpected non-empty URI query parameter value `%s' under key `%s'\n", 762 have.cstr, 763 key); 764 ok = false; 765 } 766 } 767 else if (1 == 768 sscanf (tok, 769 "Q-%[^:]:%[^\n]", 770 key, 771 value)) 772 { 773 struct MHD_StringNullable have; 774 775 if (! MHD_request_get_value (request, 776 MHD_VK_URI_QUERY_PARAM, 777 key, 778 &have)) 779 { 780 fprintf (stderr, 781 "Expected URI query parameter `%s' missing\n", 782 key); 783 ok = false; 784 } 785 else if (NULL != have.cstr) 786 { 787 fprintf (stderr, 788 "Unexpected non-NULL URI query parameter value `%s' under key `%s'\n", 789 have.cstr, 790 key); 791 ok = false; 792 } 793 } 794 else if (1 == 795 sscanf (tok, 796 "Q-:%[^\n]", 797 value)) 798 { 799 struct MHD_StringNullable have; 800 801 if (! MHD_request_get_value (request, 802 MHD_VK_URI_QUERY_PARAM, 803 "", 804 &have)) 805 { 806 fprintf (stderr, 807 "Expected URI query parameter without key missing\n"); 808 ok = false; 809 } 810 else if ( (NULL == have.cstr) || 811 (0 != strcmp (have.cstr, 812 value)) ) 813 { 814 fprintf (stderr, 815 "Wrong URI query parameter value `%s' under missing key, expected `%s'\n", 816 have.cstr, 817 value); 818 ok = false; 819 } 820 } 821 else 822 { 823 fprintf (stderr, 824 "Invalid token `%s' in test specification\n", 825 tok); 826 ok = false; 827 } 828 } 829 free (hdr); 830 return ok; 831 } 832 833 834 /** 835 * A client has requested the given url using the given method 836 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 837 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 838 * If @a upload_size is not zero and response action is provided by this 839 * callback, then upload will be discarded and the stream (the connection for 840 * HTTP/1.1) will be closed after sending the response. 841 * 842 * @param cls argument given together with the function 843 * pointer when the handler was registered with MHD 844 * @param request the request object 845 * @param path the requested uri (without arguments after "?") 846 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 847 * #MHD_HTTP_METHOD_PUT, etc.) 848 * @param upload_size the size of the message upload content payload, 849 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 850 * final chunk has not been processed yet) 851 * @return action how to proceed, NULL 852 * if the request must be aborted due to a serious 853 * error while handling the request (implies closure 854 * of underling data stream, for HTTP/1.1 it means 855 * socket closure). 856 */ 857 static const struct MHD_Action * 858 server_req_cb (void *cls, 859 struct MHD_Request *MHD_RESTRICT request, 860 const struct MHD_String *MHD_RESTRICT path, 861 enum MHD_HTTP_Method method, 862 uint_fast64_t upload_size) 863 { 864 bool failed = false; 865 866 if (current->expect_method != method) 867 { 868 fprintf (stderr, 869 "Wrong HTTP method\n"); 870 failed = true; 871 } 872 if (0 != strcmp (current->expect_path, 873 path->cstr)) 874 { 875 fprintf (stderr, 876 "Wrong HTTP path %s\n", 877 path->cstr); 878 failed = true; 879 } 880 if (current->expect_upload_size != 881 upload_size) 882 { 883 fprintf (stderr, 884 "Wrong HTTP path %s\n", 885 path->cstr); 886 failed = true; 887 } 888 if ( (NULL != current->expect_parser) && 889 (! check_headers (request, 890 current->expect_parser)) ) 891 failed = true; 892 if (failed) 893 return NULL; 894 return MHD_action_from_response ( 895 request, 896 MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT)); 897 } 898 899 900 /** 901 * Helper function to deal with partial writes. 902 * Fails hard (calls exit() on failures)! 903 * 904 * @param fd where to write to 905 * @param buf what to write 906 * @param buf_size number of bytes in @a buf 907 */ 908 static void 909 write_all (int fd, 910 const void *buf, 911 size_t buf_size) 912 { 913 const char *cbuf = buf; 914 size_t off; 915 916 off = 0; 917 while (off < buf_size) 918 { 919 ssize_t ret; 920 921 ret = write (fd, 922 &cbuf[off], 923 buf_size - off); 924 if (ret <= 0) 925 { 926 fprintf (stderr, 927 "Writing %u bytes to %d failed: %s\n", 928 (unsigned int) (buf_size - off), 929 fd, 930 strerror (errno)); 931 exit (1); 932 } 933 off += ret; 934 } 935 } 936 937 938 static int 939 run_test () 940 { 941 int s; 942 struct sockaddr_in sa = { 943 .sin_family = AF_INET, 944 .sin_port = htons (port), 945 }; 946 char dummy; 947 948 s = socket (AF_INET, SOCK_STREAM, 0); 949 if (-1 == s) 950 { 951 fprintf (stderr, 952 "socket() failed: %s\n", 953 strerror (errno)); 954 return 1; 955 } 956 inet_pton (AF_INET, 957 "127.0.0.1", 958 &sa.sin_addr); 959 if (0 != connect (s, 960 (struct sockaddr *) &sa, 961 sizeof (sa))) 962 { 963 fprintf (stderr, 964 "bind() failed: %s\n", 965 strerror (errno)); 966 close (s); 967 return 1; 968 } 969 write_all (s, 970 current->upload, 971 strlen (current->upload)); 972 shutdown (s, 973 SHUT_WR); 974 if (sizeof (dummy) != 975 read (s, 976 &dummy, 977 sizeof (dummy))) 978 { 979 fprintf (stderr, 980 "Server closed connection due to error!\n"); 981 close (s); 982 return 1; 983 } 984 close (s); 985 return 0; 986 } 987 988 989 static int 990 run_tests (void) 991 { 992 for (unsigned int i = 0; 993 NULL != tests[i].name; 994 i++) 995 { 996 current = &tests[i]; 997 if (0 != run_test ()) 998 { 999 fprintf (stderr, 1000 "Test `%s' failed\n", 1001 current->name); 1002 return 1; 1003 } 1004 } 1005 return 0; 1006 } 1007 1008 1009 int 1010 main () 1011 { 1012 struct MHD_Daemon *d; 1013 1014 d = MHD_daemon_create (&server_req_cb, 1015 NULL); 1016 if (MHD_SC_OK != 1017 MHD_DAEMON_SET_OPTIONS ( 1018 d, 1019 MHD_D_OPTION_WM_WORKER_THREADS (2), 1020 MHD_D_OPTION_DEFAULT_TIMEOUT (1), 1021 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 1022 0))) 1023 { 1024 fprintf (stderr, 1025 "Failed to configure daemon!"); 1026 return 1; 1027 } 1028 1029 { 1030 enum MHD_StatusCode sc; 1031 1032 sc = MHD_daemon_start (d); 1033 if (MHD_SC_OK != sc) 1034 { 1035 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 1036 fprintf (stderr, 1037 "Failed to start server: %s\n", 1038 MHD_status_code_to_string_lazy (sc)); 1039 #else 1040 fprintf (stderr, 1041 "Failed to start server: %u\n", 1042 (unsigned int) sc); 1043 #endif 1044 MHD_daemon_destroy (d); 1045 return 1; 1046 } 1047 } 1048 1049 { 1050 union MHD_DaemonInfoFixedData info; 1051 enum MHD_StatusCode sc; 1052 1053 sc = MHD_daemon_get_info_fixed ( 1054 d, 1055 MHD_DAEMON_INFO_FIXED_BIND_PORT, 1056 &info); 1057 if (MHD_SC_OK != sc) 1058 { 1059 fprintf (stderr, 1060 "Failed to determine our port: %u\n", 1061 (unsigned int) sc); 1062 MHD_daemon_destroy (d); 1063 return 1; 1064 } 1065 port = info.v_bind_port_uint16; 1066 } 1067 1068 { 1069 int result; 1070 1071 result = run_tests (); 1072 MHD_daemon_destroy (d); 1073 return result; 1074 } 1075 }