libmicrohttpd2

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

test_raw.c (31393B)


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