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