test_raw.c (31677B)
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 #if BUG 528 { 529 .name = "Absolute form", 530 // RFC 9112 Section 3.2.2 531 .upload = "GET http://example.com/ HTTP/1.1\r\nHost: example.bad\r\n\r\n", 532 .expect_method = MHD_HTTP_METHOD_GET, 533 .expect_path = "/", 534 .expect_parser = "H-Host:example.com", 535 }, 536 #endif 537 { 538 .name = NULL, 539 } 540 }; 541 542 543 static struct Test *current; 544 545 546 /** 547 * Our port. 548 */ 549 static uint16_t port; 550 551 552 static bool 553 check_headers (struct MHD_Request *MHD_RESTRICT request, 554 const char *spec) 555 { 556 bool ok = true; 557 char *hdr = strdup (spec); 558 char *tok; 559 char key[strlen (spec)]; 560 char value[strlen (spec)]; 561 562 for (tok = strtok (hdr, 563 "\n"); 564 NULL != tok; 565 tok = strtok (NULL, 566 "\n")) 567 { 568 char dummy; 569 570 if (2 == 571 sscanf (tok, 572 "H-%[^:]:%[^\n]s", 573 key, 574 value)) 575 { 576 struct MHD_StringNullable have; 577 578 if (! MHD_request_get_value (request, 579 MHD_VK_HEADER, 580 key, 581 &have)) 582 { 583 fprintf (stderr, 584 "Expected header `%s' missing\n", 585 key); 586 ok = false; 587 } 588 else if ( (NULL == have.cstr) || 589 (0 != strcmp (have.cstr, 590 value)) ) 591 { 592 fprintf (stderr, 593 "Wrong header value `%s' under key `%s', expected `%s'\n", 594 have.cstr, 595 key, 596 value); 597 ok = false; 598 } 599 } 600 else if (2 == 601 sscanf (tok, 602 "C-%[^:]:%[^\n]", 603 key, 604 value)) 605 { 606 struct MHD_StringNullable have; 607 608 if (! MHD_request_get_value (request, 609 MHD_VK_COOKIE, 610 key, 611 &have)) 612 { 613 fprintf (stderr, 614 "Expected cookie `%s' missing\n", 615 key); 616 ok = false; 617 } 618 else if ( (NULL == have.cstr) || 619 (0 != strcmp (have.cstr, 620 value)) ) 621 { 622 fprintf (stderr, 623 "Wrong cookie value `%s' under key `%s', expected `%s'\n", 624 have.cstr, 625 key, 626 value); 627 ok = false; 628 } 629 } 630 else if (2 == 631 sscanf (tok, 632 "Q-%[^:]:%[^\n]", 633 key, 634 value)) 635 { 636 struct MHD_StringNullable have; 637 638 if (! MHD_request_get_value (request, 639 MHD_VK_URI_QUERY_PARAM, 640 key, 641 &have)) 642 { 643 fprintf (stderr, 644 "Expected URI query parameter `%s' missing\n", 645 key); 646 ok = false; 647 } 648 else if ( (NULL == have.cstr) || 649 (0 != strcmp (have.cstr, 650 value)) ) 651 { 652 fprintf (stderr, 653 "Wrong URI query parameter value `%s' under key `%s', expected `%s'\n", 654 have.cstr, 655 key, 656 value); 657 ok = false; 658 } 659 } 660 else if (1 == 661 sscanf (tok, 662 "H-%[^:]:%[^\n]", 663 key, 664 value)) 665 { 666 struct MHD_StringNullable have; 667 668 if (! MHD_request_get_value (request, 669 MHD_VK_HEADER, 670 key, 671 &have)) 672 { 673 fprintf (stderr, 674 "Expected header `%s' missing\n", 675 key); 676 ok = false; 677 } /* HTTP header can never be "NULL", only empty string */ 678 else if ( (NULL == have.cstr) || 679 (0 != strcmp (have.cstr, 680 "")) ) 681 { 682 fprintf (stderr, 683 "Unexpected non-empty header value `%s' under key `%s'\n", 684 have.cstr, 685 key); 686 ok = false; 687 } 688 } 689 else if (2 == 690 sscanf (tok, 691 "C-%[^:]%c%[^\n]", 692 key, 693 &dummy, 694 value)) 695 { 696 struct MHD_StringNullable have; 697 698 if (! MHD_request_get_value (request, 699 MHD_VK_COOKIE, 700 key, 701 &have)) 702 { 703 fprintf (stderr, 704 "Expected cookie `%s' missing\n", 705 key); 706 ok = false; 707 } 708 else if ( (NULL == have.cstr) || 709 (0 != strcmp (have.cstr, 710 "")) ) 711 { 712 fprintf (stderr, 713 "Unexpected non-empty cookie value `%s' under key `%s'\n", 714 have.cstr, 715 key); 716 ok = false; 717 } 718 } 719 else if (1 == 720 sscanf (tok, 721 "C-%[^:]:%[^\n]", 722 key, 723 value)) 724 { 725 struct MHD_StringNullable have; 726 727 if (! MHD_request_get_value (request, 728 MHD_VK_COOKIE, 729 key, 730 &have)) 731 { 732 fprintf (stderr, 733 "Expected cookie `%s' missing\n", 734 key); 735 ok = false; 736 } 737 else if (NULL != have.cstr) 738 { 739 fprintf (stderr, 740 "Unexpected non-NULL cookie value `%s' under key `%s'\n", 741 have.cstr, 742 key); 743 ok = false; 744 } 745 } 746 else if (2 == 747 sscanf (tok, 748 "Q-%[^:]%c%[^\n]", 749 key, 750 &dummy, 751 value)) 752 { 753 struct MHD_StringNullable have; 754 755 if (! MHD_request_get_value (request, 756 MHD_VK_URI_QUERY_PARAM, 757 key, 758 &have)) 759 { 760 fprintf (stderr, 761 "Expected URI query parameter `%s' missing\n", 762 key); 763 ok = false; 764 } 765 else if ( (NULL == have.cstr) || 766 (0 != strcmp (have.cstr, 767 "")) ) 768 { 769 fprintf (stderr, 770 "Unexpected non-empty URI query parameter value `%s' under key `%s'\n", 771 have.cstr, 772 key); 773 ok = false; 774 } 775 } 776 else if (1 == 777 sscanf (tok, 778 "Q-%[^:]:%[^\n]", 779 key, 780 value)) 781 { 782 struct MHD_StringNullable have; 783 784 if (! MHD_request_get_value (request, 785 MHD_VK_URI_QUERY_PARAM, 786 key, 787 &have)) 788 { 789 fprintf (stderr, 790 "Expected URI query parameter `%s' missing\n", 791 key); 792 ok = false; 793 } 794 else if (NULL != have.cstr) 795 { 796 fprintf (stderr, 797 "Unexpected non-NULL URI query parameter value `%s' under key `%s'\n", 798 have.cstr, 799 key); 800 ok = false; 801 } 802 } 803 else if (1 == 804 sscanf (tok, 805 "Q-:%[^\n]", 806 value)) 807 { 808 struct MHD_StringNullable have; 809 810 if (! MHD_request_get_value (request, 811 MHD_VK_URI_QUERY_PARAM, 812 "", 813 &have)) 814 { 815 fprintf (stderr, 816 "Expected URI query parameter without key missing\n"); 817 ok = false; 818 } 819 else if ( (NULL == have.cstr) || 820 (0 != strcmp (have.cstr, 821 value)) ) 822 { 823 fprintf (stderr, 824 "Wrong URI query parameter value `%s' under missing key, expected `%s'\n", 825 have.cstr, 826 value); 827 ok = false; 828 } 829 } 830 else 831 { 832 fprintf (stderr, 833 "Invalid token `%s' in test specification\n", 834 tok); 835 ok = false; 836 } 837 } 838 free (hdr); 839 return ok; 840 } 841 842 843 /** 844 * A client has requested the given url using the given method 845 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 846 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 847 * If @a upload_size is not zero and response action is provided by this 848 * callback, then upload will be discarded and the stream (the connection for 849 * HTTP/1.1) will be closed after sending the response. 850 * 851 * @param cls argument given together with the function 852 * pointer when the handler was registered with MHD 853 * @param request the request object 854 * @param path the requested uri (without arguments after "?") 855 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 856 * #MHD_HTTP_METHOD_PUT, etc.) 857 * @param upload_size the size of the message upload content payload, 858 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 859 * final chunk has not been processed yet) 860 * @return action how to proceed, NULL 861 * if the request must be aborted due to a serious 862 * error while handling the request (implies closure 863 * of underling data stream, for HTTP/1.1 it means 864 * socket closure). 865 */ 866 static const struct MHD_Action * 867 server_req_cb (void *cls, 868 struct MHD_Request *MHD_RESTRICT request, 869 const struct MHD_String *MHD_RESTRICT path, 870 enum MHD_HTTP_Method method, 871 uint_fast64_t upload_size) 872 { 873 bool failed = false; 874 875 if (current->expect_method != method) 876 { 877 fprintf (stderr, 878 "Wrong HTTP method\n"); 879 failed = true; 880 } 881 if (0 != strcmp (current->expect_path, 882 path->cstr)) 883 { 884 fprintf (stderr, 885 "Wrong HTTP path %s\n", 886 path->cstr); 887 failed = true; 888 } 889 if (current->expect_upload_size != 890 upload_size) 891 { 892 fprintf (stderr, 893 "Wrong HTTP path %s\n", 894 path->cstr); 895 failed = true; 896 } 897 if ( (NULL != current->expect_parser) && 898 (! check_headers (request, 899 current->expect_parser)) ) 900 failed = true; 901 if (failed) 902 return NULL; 903 return MHD_action_from_response ( 904 request, 905 MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT)); 906 } 907 908 909 /** 910 * Helper function to deal with partial writes. 911 * Fails hard (calls exit() on failures)! 912 * 913 * @param fd where to write to 914 * @param buf what to write 915 * @param buf_size number of bytes in @a buf 916 */ 917 static void 918 write_all (int fd, 919 const void *buf, 920 size_t buf_size) 921 { 922 const char *cbuf = buf; 923 size_t off; 924 925 off = 0; 926 while (off < buf_size) 927 { 928 ssize_t ret; 929 930 ret = write (fd, 931 &cbuf[off], 932 buf_size - off); 933 if (ret <= 0) 934 { 935 fprintf (stderr, 936 "Writing %u bytes to %d failed: %s\n", 937 (unsigned int) (buf_size - off), 938 fd, 939 strerror (errno)); 940 exit (1); 941 } 942 off += ret; 943 } 944 } 945 946 947 static int 948 run_test () 949 { 950 int s; 951 struct sockaddr_in sa = { 952 .sin_family = AF_INET, 953 .sin_port = htons (port), 954 }; 955 char dummy; 956 957 s = socket (AF_INET, SOCK_STREAM, 0); 958 if (-1 == s) 959 { 960 fprintf (stderr, 961 "socket() failed: %s\n", 962 strerror (errno)); 963 return 1; 964 } 965 inet_pton (AF_INET, 966 "127.0.0.1", 967 &sa.sin_addr); 968 if (0 != connect (s, 969 (struct sockaddr *) &sa, 970 sizeof (sa))) 971 { 972 fprintf (stderr, 973 "bind() failed: %s\n", 974 strerror (errno)); 975 close (s); 976 return 1; 977 } 978 write_all (s, 979 current->upload, 980 strlen (current->upload)); 981 shutdown (s, 982 SHUT_WR); 983 if (sizeof (dummy) != 984 read (s, 985 &dummy, 986 sizeof (dummy))) 987 { 988 fprintf (stderr, 989 "Server closed connection due to error!\n"); 990 close (s); 991 return 1; 992 } 993 close (s); 994 return 0; 995 } 996 997 998 static int 999 run_tests (void) 1000 { 1001 for (unsigned int i = 0; 1002 NULL != tests[i].name; 1003 i++) 1004 { 1005 current = &tests[i]; 1006 if (0 != run_test ()) 1007 { 1008 fprintf (stderr, 1009 "Test `%s' failed\n", 1010 current->name); 1011 return 1; 1012 } 1013 } 1014 return 0; 1015 } 1016 1017 1018 int 1019 main (void) 1020 { 1021 struct MHD_Daemon *d; 1022 1023 d = MHD_daemon_create (&server_req_cb, 1024 NULL); 1025 if (MHD_SC_OK != 1026 MHD_DAEMON_SET_OPTIONS ( 1027 d, 1028 MHD_D_OPTION_WM_WORKER_THREADS (2), 1029 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1500), 1030 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 1031 0))) 1032 { 1033 fprintf (stderr, 1034 "Failed to configure daemon!"); 1035 return 1; 1036 } 1037 1038 { 1039 enum MHD_StatusCode sc; 1040 1041 sc = MHD_daemon_start (d); 1042 if (MHD_SC_OK != sc) 1043 { 1044 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 1045 fprintf (stderr, 1046 "Failed to start server: %s\n", 1047 MHD_status_code_to_string_lazy (sc)); 1048 #else 1049 fprintf (stderr, 1050 "Failed to start server: %u\n", 1051 (unsigned int) sc); 1052 #endif 1053 MHD_daemon_destroy (d); 1054 return 1; 1055 } 1056 } 1057 1058 { 1059 union MHD_DaemonInfoFixedData info; 1060 enum MHD_StatusCode sc; 1061 1062 sc = MHD_daemon_get_info_fixed ( 1063 d, 1064 MHD_DAEMON_INFO_FIXED_BIND_PORT, 1065 &info); 1066 if (MHD_SC_OK != sc) 1067 { 1068 fprintf (stderr, 1069 "Failed to determine our port: %u\n", 1070 (unsigned int) sc); 1071 MHD_daemon_destroy (d); 1072 return 1; 1073 } 1074 port = info.v_bind_port_uint16; 1075 } 1076 1077 { 1078 int result; 1079 1080 result = run_tests (); 1081 MHD_daemon_destroy (d); 1082 return result; 1083 } 1084 }