libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

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 }