libmicrohttpd2

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

test_incompatible.c (31537B)


      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_incompatible.c
     41  * @brief tests server rejects incorrect or non-standard requests, either
     42  *   those that are:
     43  *   - incompatible to MUST requirements, or
     44  *   - non-standard and violate SHOULD requirements
     45  * @author Christian Grothoff
     46  */
     47 #include <stdio.h>
     48 #include <stdbool.h>
     49 #include <errno.h>
     50 #include <string.h>
     51 #include <stdlib.h>
     52 #include <unistd.h>
     53 #include <arpa/inet.h>
     54 #include <netinet/ip.h>
     55 #include "microhttpd2.h"
     56 
     57 #define LOG 0
     58 
     59 /**
     60  * Defines a test.
     61  */
     62 struct Test
     63 {
     64   /**
     65    * Human-readable name of the test. NULL to end test array.
     66    */
     67   const char *name;
     68 
     69   /**
     70    * Request to send to the server.
     71    */
     72   const char *upload;
     73 
     74 };
     75 
     76 
     77 /**
     78  * Tests with HTTP requests that violate MUST constraints
     79  * of the HTTP specifications.
     80  *
     81  * For example, using a bare LF instead of CRLF is forbidden, and
     82  * requests that include both a "Transfer-Encoding:" and a
     83  * "Content-Length:" headers are rejected.
     84  */
     85 static struct Test tests_must[] = {
     86   {
     87     .name = "HTTP 1.1 without Host",
     88     .upload = "GET / HTTP/1.1\r\n\r\n",
     89   },
     90   {
     91     .name = "HTTP 1.0 GET without CRLF",
     92     .upload = "GET / HTTP/1.0\n\n",
     93   },
     94   {
     95     .name = "POST with both Content-Length and Transfer-Encoding",
     96     .upload =
     97       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n",
     98   },
     99   {
    100     .name = "unsupported Ttransfer-Encoding",
    101     .upload =
    102       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: wild\r\n\r\n0\r\n",
    103   },
    104   {
    105     .name = "Invalid HTTP version format",
    106     .upload = "GET / HTTP/1\r\nHost: example.com\r\n\r\n",
    107     // RFC 9112 Section 2.3: HTTP-version must be "HTTP/" followed by two digits separated by "."
    108   },
    109   {
    110     .name = "Missing space after method",
    111     .upload = "GET/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
    112     // RFC 9112 Section 3: Request-line requires SP between method and request-target
    113   },
    114   {
    115     .name = "Invalid request-target with space",
    116     .upload = "GET /path with space HTTP/1.1\r\nHost: example.com\r\n\r\n",
    117     // RFC 9112 Section 3.2: Request-target must not contain unencoded spaces
    118   },
    119   {
    120     .name = "Header field name with space",
    121     .upload = "GET / HTTP/1.1\r\nHost Name: example.com\r\n\r\n",
    122     // RFC 9110 Section 5.1: Field names must be tokens (no spaces allowed)
    123   },
    124 #ifdef MORE_PEER_VALIDATION
    125   {
    126     .name = "Header field name with colon",
    127     .upload = "GET / HTTP/1.1\r\nHost:Name: example.com\r\n\r\n",
    128     // RFC 9110 Section 5.1: Field names must be tokens (colons not allowed)
    129   },
    130 #endif
    131   {
    132     .name = "Missing colon after header field name",
    133     .upload = "GET / HTTP/1.1\r\nHost example.com\r\n\r\n",
    134     // RFC 9112 Section 5: Header field must have name, colon, and value
    135   },
    136   {
    137     .name = "Header line ending with bare CR",
    138     .upload = "GET / HTTP/1.1\rHost: example.com\r\n\r\n",
    139     // RFC 9112 Section 2.2: Lines must end with CRLF, not bare CR
    140   },
    141   {
    142     .name = "Request line ending with bare LF",
    143     .upload = "GET / HTTP/1.1\nHost: example.com\r\n\r\n",
    144     // RFC 9112 Section 2.2: Request-line must end with CRLF
    145   },
    146   {
    147     .name = "Multiple Host headers",
    148     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nHost: other.com\r\n\r\n",
    149     // RFC 9112 Section 3.2: A sender MUST NOT generate multiple Host header fields
    150   },
    151   {
    152     .name = "Negative Content-Length",
    153     .upload =
    154       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: -5\r\n\r\n",
    155     // RFC 9110 Section 8.6: Content-Length value must be non-negative decimal integer
    156   },
    157   {
    158     .name = "Non-numeric Content-Length",
    159     .upload =
    160       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: abc\r\n\r\n",
    161     // RFC 9110 Section 8.6: Content-Length must be a decimal integer
    162   },
    163   {
    164     .name = "Multiple Content-Length with different values",
    165     .upload =
    166       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 5\r\nContent-Length: 10\r\n\r\n",
    167     // RFC 9110 Section 8.6: Multiple Content-Length values must be identical
    168   },
    169 #ifdef MORE_PEER_VALIDATION
    170   {
    171     .name = "Invalid method with control character",
    172     .upload = "GET\x01 / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    173     // RFC 9110 Section 9.1: Method token must not contain control characters
    174   },
    175 #endif
    176   {
    177     .name = "Request-target starting with space",
    178     .upload = "GET  / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    179     // RFC 9112 Section 3: Only single SP allowed between method and request-target
    180   },
    181   {
    182     .name = "HTTP/0.9 simple request with headers",
    183     .upload = "GET /\r\nHost: example.com\r\n\r\n",
    184     // RFC 9112 Section 2.3: HTTP/0.9 requests must not have headers
    185   },
    186   {
    187     .name = "Missing final CRLF after headers",
    188     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n",
    189     // RFC 9112 Section 6.1: Empty line (CRLF) required after headers
    190   },
    191   {
    192     .name = "Whitespace before header field name",
    193     .upload = "GET / HTTP/1.1\r\n Host: example.com\r\n\r\n",
    194     // RFC 9112 Section 5: No whitespace allowed before field name
    195   },
    196 
    197 
    198   {
    199     .name = "Empty request line",
    200     .upload = "\r\n\r\n",
    201     // RFC 9112 Section 3: Request-line is required
    202   },
    203   {
    204     .name = "Request line with only method",
    205     .upload = "GET\r\n\r\n",
    206     // RFC 9112 Section 3: Request-line must have method, target, and version
    207   },
    208   {
    209     .name = "Request line with only method and target",
    210     .upload = "GET /\r\n\r\n",
    211     // RFC 9112 Section 3: HTTP-version is required in request-line
    212   },
    213   {
    214     .name = "Request with only CR as line ending",
    215     .upload = "GET / HTTP/1.1\rHost: example.com\r\r",
    216     // RFC 9112 Section 2.2: CRLF required, not bare CR
    217   },
    218   {
    219     .name = "Missing space before HTTP version",
    220     .upload = "GET /HTTP/1.1\r\nHost: example.com\r\n\r\n",
    221     // RFC 9112 Section 3: SP required between request-target and HTTP-version
    222   },
    223   {
    224     .name = "HTTP version with extra dot",
    225     .upload = "GET / HTTP/1.1.0\r\nHost: example.com\r\n\r\n",
    226     // RFC 9112 Section 2.3: Version format is "HTTP/" DIGIT "." DIGIT
    227   },
    228   {
    229     .name = "HTTP version with letter",
    230     .upload = "GET / HTTP/1.A\r\nHost: example.com\r\n\r\n",
    231     // RFC 9112 Section 2.3: Version numbers must be digits
    232   },
    233 #ifdef NOT_A_BUG
    234   {
    235     .name = "Method with lowercase letters",
    236     .upload = "get / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    237     // RFC 9110 Section 9.1: Method is case-sensitive
    238     /* This a valid non-standard ("custom") method. */
    239   },
    240 #endif
    241 #ifdef MORE_PEER_VALIDATION
    242   {
    243     .name = "Request-target with fragment identifier",
    244     .upload = "GET /path#fragment HTTP/1.1\r\nHost: example.com\r\n\r\n",
    245     // RFC 9112 Section 3.2.1: Fragment must not be sent in request-target
    246   },
    247 #endif
    248 #ifdef OTHER_USES
    249   {
    250     .name = "Absolute-form with userinfo in HTTP/1.1",
    251     .upload =
    252       "GET http://user:pass@example.com/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
    253     // RFC 9110 Section 4.2.4: Userinfo (and its "@" delimiter) is now disallowed
    254     /* This is a valid string for HTTP proxy */
    255   },
    256 #endif
    257   {
    258     .name = "Request-target with bare CR",
    259     .upload = "GET /path\r/file HTTP/1.1\r\nHost: example.com\r\n\r\n",
    260     // RFC 9112 Section 3.2: Request-target must not contain CR
    261   },
    262   {
    263     .name = "Request-target with LF",
    264     .upload = "GET /path\n/file HTTP/1.1\r\nHost: example.com\r\n\r\n",
    265     // RFC 9112 Section 3.2: Request-target must not contain LF
    266   },
    267   {
    268     .name = "Header colon without field name",
    269     .upload = "GET / HTTP/1.1\r\n: value\r\nHost: example.com\r\n\r\n",
    270     // RFC 9110 Section 5.1: Field name is required before colon
    271   },
    272   {
    273     .name = "Content-Length with plus sign",
    274     .upload =
    275       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: +10\r\n\r\n0123456789",
    276     // RFC 9110 Section 8.6: Content-Length must be 1*DIGIT (no sign allowed)
    277   },
    278   {
    279     .name = "Content-Length with whitespace",
    280     .upload =
    281       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 1 0\r\n\r\n0123456789",
    282     // RFC 9110 Section 8.6: Content-Length must be digits only
    283   },
    284   {
    285     .name = "Content-Length with decimal point",
    286     .upload =
    287       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 10.0\r\n\r\n0123456789",
    288     // RFC 9110 Section 8.6: Content-Length must be decimal integer, not floating point
    289   },
    290   {
    291     .name = "Content-Length overflow value",
    292     .upload =
    293       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 99999999999999999999999999999\r\n\r\n",
    294     // RFC 9110 Section 8.6: Content-Length must be valid decimal integer
    295   },
    296 #ifdef MORE_PEER_VALIDATION
    297   {
    298     .name = "Request with vertical tab in header",
    299     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom:\vvalue\r\n\r\n",
    300     // RFC 9110 Section 5.5: Only HTAB, SP, and VCHAR allowed in field values (VT is 0x0B)
    301   },
    302 #endif
    303 #ifdef MORE_PEER_VALIDATION
    304   {
    305     .name = "Request with form feed in header value",
    306     .upload =
    307       "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom: val\fue\r\n\r\n",
    308     // RFC 9110 Section 5.5: Form feed (0x0C) not allowed in field values
    309   },
    310 #endif
    311   {
    312     .name = "Transfer-Encoding with unknown coding",
    313     .upload =
    314       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: gzip\r\n\r\n",
    315     // RFC 9112 Section 6.1: Only 'chunked' and specific codings defined
    316   },
    317   {
    318     .name = "Transfer-Encoding chunked not last",
    319     .upload =
    320       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked, gzip\r\n\r\n",
    321     // RFC 9112 Section 6.1: 'chunked' must be last when present
    322   },
    323 #ifdef SPECIAL_STRICT_PEER_CHECKING
    324   {
    325     .name = "HTTP/1.0 with Transfer-Encoding",
    326     .upload =
    327       "POST / HTTP/1.0\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n",
    328     // RFC 9112 Section 6.1: Transfer-Encoding must not be sent in HTTP/1.0
    329     /* RFC does not enforce the server to reject such requests. "Must not be sent" != "Must be rejected" */
    330   },
    331 #endif
    332 #ifdef NOT_A_BUG
    333   {
    334     .name = "POST without Content-Length or Transfer-Encoding",
    335     .upload = "POST / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    336     // RFC 9112 Section 6.3: Message with body must have Content-Length or Transfer-Encoding
    337     /* This is a perfectly valid request with an empty body. */
    338   },
    339 #endif
    340 #ifdef MORE_PEER_VALIDATION
    341   {
    342     .name = "Request with CTL character in header name",
    343     .upload =
    344       "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cu\x01stom: value\r\n\r\n",
    345     // RFC 9110 Section 5.1: Field names must be tokens, CTL chars not allowed
    346   },
    347 #endif
    348 #ifdef MORE_PEER_VALIDATION
    349   {
    350     .name = "Request with DEL character in header name",
    351     .upload =
    352       "GET / HTTP/1.1\r\nHost: example.com\r\nX-Custom\x7F: value\r\n\r\n",
    353     // RFC 9110 Section 5.1: DEL (0x7F) not allowed in field names
    354   },
    355 #endif
    356 #ifdef MORE_PEER_VALIDATION
    357   {
    358     .name = "Request with non-ASCII in header name",
    359     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Cüstom: value\r\n\r\n",
    360     // RFC 9110 Section 5.1: Field names must be ASCII tokens
    361   },
    362 #endif
    363 #ifdef SPECIAL_STRICT_PEER_CHECKING
    364   {
    365     .name = "Request-target as asterisk for non-OPTIONS",
    366     .upload = "GET * HTTP/1.1\r\nHost: example.com\r\n\r\n",
    367     // RFC 9112 Section 3.2.4: Asterisk form only valid for OPTIONS
    368   },
    369 #endif
    370 #ifdef MORE_PEER_VALIDATION
    371   {
    372     .name = "Authority-form for non-CONNECT",
    373     .upload = "GET example.com:80 HTTP/1.1\r\nHost: example.com\r\n\r\n",
    374     // RFC 9112 Section 3.2.3: Authority-form only valid for CONNECT
    375   },
    376 #endif
    377   {
    378     .name = "HTTP/1.1 GET with empty Host header",
    379     .upload = "GET / HTTP/1.1\r\nHost: \r\n\r\n",
    380     // RFC 9112 Section 3.2: Empty Host header is invalid for this target form
    381   },
    382 #ifdef MORE_PEER_VALIDATION
    383   {
    384     .name = "Request with quoted string in field name",
    385     .upload =
    386       "GET / HTTP/1.1\r\nHost: example.com\r\n\"X-Custom\": value\r\n\r\n",
    387     // RFC 9110 Section 5.1: Field names must be tokens, not quoted strings
    388   },
    389 #endif
    390 #if 0 /* NOT A BUG */
    391   {
    392     .name = "HTTP/1.1 request with HTTP/1.2 version",
    393     .upload = "GET / HTTP/1.2\r\nHost: example.com\r\n\r\n",
    394     /* HTTP/1.2 SHOULD be processed as 1.1 if server supports 1.1.
    395        RFC 9110, Section 6.2
    396        See https://www.rfc-editor.org/rfc/rfc9110.html#name-control-data */
    397   },
    398 #endif
    399   {
    400     .name = "HTTP/1.1 request with HTTP/0.o version",
    401     .upload = "GET / HTTP/0.o\r\nHost: example.com\r\n\r\n",
    402     /* Version must be "digit dot digit"
    403      * RFC 9112, Section 2.3 */
    404   },
    405   {
    406     .name = "HTTP/1.1 request with HTTP/0.9 version",
    407     .upload = "GET / HTTP/0.9\r\nHost: example.com\r\n\r\n",
    408     /* HTTP versions before HTTP/1.0 are not supported */
    409   },
    410   {
    411     .name = "HTTP/1.1 request with HTTP/1.10 version",
    412     .upload = "GET / HTTP/1.10\r\nHost: example.com\r\n\r\n",
    413     /* Version must be "digit dot digit"
    414      * RFC 9112, Section 2.3 */
    415   },
    416   {
    417     .name = "HTTP/1.1 request with HTTP/1.I version",
    418     .upload = "GET / HTTP/1.I\r\nHost: example.com\r\n\r\n",
    419     /* Version must be "digit dot digit"
    420      * RFC 9112, Section 2.3 */
    421   },
    422   {
    423     .name = "HTTP/1.1 request with HTTP/1,1 version",
    424     .upload = "GET / HTTP/1,1\r\nHost: example.com\r\n\r\n",
    425     /* Version must be "digit dot digit"
    426      * RFC 9112, Section 2.3 */
    427   },
    428   {
    429     .name = "HTTP version HTTP/2.0 on HTTP/1.1 connection",
    430     .upload = "GET / HTTP/2.0\r\nHost: example.com\r\n\r\n",
    431     // RFC 9112 Section 2.3: HTTP/2 uses different framing, not text-based
    432   },
    433   {
    434     .name = "Empty method",
    435     .upload = " / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    436     // RFC 9112 Section 3: Method is required
    437   },
    438 #ifdef MORE_PEER_VALIDATION
    439   {
    440     .name = "Method with special character",
    441     .upload = "GET/ / HTTP/1.1\r\nHost: example.com\r\n\r\n",
    442     // RFC 9110 Section 9.1: Method must be a token (no '/' in method name)
    443   },
    444 #endif
    445   {
    446     .name = "Triple Host headers",
    447     .upload =
    448       "GET / HTTP/1.1\r\nHost: a.com\r\nHost: b.com\r\nHost: c.com\r\n\r\n",
    449     // RFC 9112 Section 3.2: Multiple Host headers forbidden
    450   },
    451   {
    452     .name = "Content-Length with hexadecimal",
    453     .upload =
    454       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0x10\r\n\r\n0123456789012345",
    455     // RFC 9110 Section 8.6: Content-Length must be decimal, not hexadecimal
    456   },
    457   {
    458     .name = NULL,
    459   }
    460 };
    461 
    462 
    463 /**
    464  * Tests with HTTP requests that violate MUST constraints
    465  * of the HTTP specifications during the upload.
    466  */
    467 static struct Test tests_must_upload[] = {
    468   {
    469     .name = "Chunked encoding with invalid hex",
    470     .upload =
    471       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nGG\r\n\r\n",
    472     // RFC 9112 Section 7.1: Chunk size must be hexadecimal
    473   },
    474   {
    475     .name = "Chunked encoding with negative size",
    476     .upload =
    477       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n-5\r\n\r\n",
    478     // RFC 9112 Section 7.1: Chunk size must be non-negative hex
    479   },
    480   {
    481     .name = "Chunked encoding missing CRLF after size",
    482     .upload =
    483       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5hello\r\n0\r\n\r\n",
    484     // RFC 9112 Section 7.1: CRLF required after chunk-size
    485   },
    486   {
    487     .name = "Chunked encoding missing CRLF after data",
    488     .upload =
    489       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello0\r\n\r\n",
    490     // RFC 9112 Section 7.1: CRLF required after chunk-data
    491   },
    492   {
    493     .name = "Chunked with no final chunk",
    494     .upload =
    495       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n",
    496     // RFC 9112 Section 7.1: Last chunk (size 0) is required
    497   },
    498   {
    499     .name = "Chunk size with leading whitespace",
    500     .upload =
    501       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n 5\r\nhello\r\n0\r\n\r\n",
    502     // RFC 9112 Section 7.1: No whitespace before chunk-size
    503   },
    504   {
    505     .name = "Chunk size exceeding data provided",
    506     .upload =
    507       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\nA\r\nhello\r\n0\r\n\r\n",
    508     // RFC 9112 Section 7.1: Chunk-size must match chunk-data length (10 bytes expected, 5 provided)
    509   },
    510   {
    511     .name = NULL,
    512   }
    513 };
    514 
    515 
    516 /**
    517  * Tests with HTTP requests that violate SHOULD constraints
    518  * of the HTTP specifications.
    519  *
    520  * For example, for chunked encoding, this level (and more restrictive
    521  * ones) forbids whitespace in chunk extensions.  For cookie parsing,
    522  * this level (and more restrictive ones) rejects the entire cookie if
    523  * even a single value within it is incorrectly encoded.
    524  */
    525 static struct Test tests_should[] = {
    526   {
    527     .name = "Obsolete line folding in header",
    528     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\n continuation\r\n\r\n",
    529     // RFC 9112 Section 5.2: Line folding is obsolete, recipients SHOULD reject or replace with SP
    530   },
    531 #ifdef NOT_A_BUG
    532   {
    533     .name = "Multiple spaces after colon in header",
    534     .upload = "GET / HTTP/1.1\r\nHost:     example.com\r\n\r\n",
    535     // RFC 9110 Section 5.6.3: Senders SHOULD NOT generate optional whitespace except as SP
    536     /* RFC 9110 Section 5.6.3: "OWS and RWS have the same semantics as a single SP."
    537      * RFC 9112 Section 5.1: "A field line value might be preceded and/or followed by optional whitespace (OWS)"
    538      * The server must parse this value as a correct value. */
    539   },
    540 #endif
    541 #ifdef NOT_A_BUG
    542   {
    543     .name = "Trailing whitespace in header value",
    544     .upload = "GET / HTTP/1.1\r\nHost: example.com   \r\n\r\n",
    545     // RFC 9110 Section 5.3: Trailing whitespace should be stripped
    546     /* RFC 9112 Section 5.1: "OWS occurring before the first non-whitespace octet of the field line value,
    547                               or after the last non-whitespace octet of the field line value, is excluded
    548                               by parsers when extracting the field line value from a field line."
    549      * This is a valid request. */
    550   },
    551 #endif
    552 #ifdef NOT_A_BUG
    553   {
    554     .name = "Leading whitespace in header value",
    555     .upload = "GET / HTTP/1.1\r\nHost:    example.com\r\n\r\n",
    556     // RFC 9110 Section 5.3: Leading whitespace should be stripped
    557     /* RFC 9112 Section 5.1: "OWS occurring before the first non-whitespace octet of the field line value,
    558                               or after the last non-whitespace octet of the field line value, is excluded
    559                               by parsers when extracting the field line value from a field line."
    560      * This is a valid request. */
    561   },
    562 #endif
    563 #ifdef NOT_A_BUG
    564   {
    565     .name = "Chunk extension with whitespace",
    566     .upload =
    567       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5 ; ext=val\r\nhello\r\n0\r\n\r\n",
    568     // RFC 9112 Section 7.1.1: Whitespace in chunk extensions should be minimal
    569     /* This is a valid request. */
    570   },
    571 #endif
    572 #ifdef NOT_A_BUG
    573   {
    574     .name = "Chunked with uppercase hex digits",
    575     .upload =
    576       "POST / HTTP/1.1\r\nHost: example.com\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nhello\r\n0\r\n\r\n",
    577     // RFC 9112 Section 7.1: Senders SHOULD use lowercase for hex digits (though uppercase is valid)
    578     /* This is a valid request. It must be parsed. */
    579   },
    580 #endif
    581 #ifdef NOT_A_BUG
    582   {
    583     .name = "Empty header field value",
    584     .upload = "GET / HTTP/1.1\r\nHost: example.com\r\nX-Empty:\r\n\r\n",
    585     // RFC 9110 Section 5.3: Empty field values are valid but some implementations may reject
    586     /* This is a valid request. It must be parsed. */
    587   },
    588 #endif
    589 #ifdef NOT_A_BUG
    590   {
    591     .name = "Header field name with uppercase and lowercase",
    592     .upload = "GET / HTTP/1.1\r\nHoSt: example.com\r\n\r\n",
    593     // RFC 9110 Section 5.1: Field names are case-insensitive, but conventional capitalization SHOULD be used
    594     /* This is a valid request. It must be parsed. */
    595   },
    596 #endif
    597   {
    598     .name = "Excessive whitespace in request line",
    599     .upload = "GET / HTTP/1.1 \r\nHost: example.com\r\n\r\n",
    600     // RFC 9112 Section 3: Trailing whitespace in request-line should not be present
    601   },
    602 #ifdef NOT_A_BUG
    603   {
    604     .name = "Content-Length with leading zeros",
    605     .upload =
    606       "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0005\r\n\r\nhello",
    607     // RFC 9110 Section 8.6: Leading zeros should not be sent
    608     /* This is a valid request. It must be parsed. */
    609   },
    610 #endif
    611 #ifdef NOT_A_BUG
    612   {
    613     .name = "Cookie header with invalid encoding in value",
    614     .upload =
    615       "GET / HTTP/1.1\r\nHost: example.com\r\nCookie: name=val ue\r\n\r\n",
    616     // RFC 6265 Section 4.2: Cookie values should be properly encoded, spaces require encoding
    617     /* Rejected completely on stricter level. On default level valid part "name=val" is used. */
    618   },
    619 #endif
    620   {
    621     .name = NULL,
    622   }
    623 };
    624 
    625 
    626 static struct Test *current;
    627 
    628 
    629 /**
    630  * Our port.
    631  */
    632 static uint16_t port;
    633 
    634 /**
    635  * Set to true if a test failed.
    636  */
    637 static volatile bool failed;
    638 
    639 
    640 /**
    641  * Function to process data uploaded by a client.
    642  *
    643  * Given that we ONLY generate incorrect/malformed upload requests,
    644  * this function should never be called.
    645  *
    646  * @param upload_cls the argument given together with the function
    647  *                   pointer when the handler was registered with MHD
    648  * @param request the request is being processed
    649  * @param content_data_size the size of the @a content_data,
    650  *                          zero when all data have been processed
    651  * @param[in] content_data the uploaded content data,
    652  *                         may be modified in the callback,
    653  *                         valid only until return from the callback,
    654  *                         NULL when all data have been processed
    655  * @return action specifying how to proceed:
    656  *         #MHD_upload_action_continue() to continue upload (for incremental
    657  *         upload processing only),
    658  *         #MHD_upload_action_suspend() to stop reading the upload until
    659  *         the request is resumed,
    660  *         #MHD_upload_action_abort_request() to close the socket,
    661  *         or a response to discard the rest of the upload and transmit
    662  *         the response
    663  * @ingroup action
    664  */
    665 static const struct MHD_UploadAction *
    666 uc_fail (void *upload_cls,
    667          struct MHD_Request *request,
    668          size_t content_data_size,
    669          void *content_data)
    670 {
    671   fprintf (stderr,
    672            "Test `%s' failed\n",
    673            current->name);
    674   failed = true;
    675   return NULL;
    676 }
    677 
    678 
    679 /**
    680  * A client has requested the given url using the given method
    681  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    682  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
    683  * If @a upload_size is not zero and response action is provided by this
    684  * callback, then upload will be discarded and the stream (the connection for
    685  * HTTP/1.1) will be closed after sending the response.
    686  *
    687  * This function is expected to be called, but when we try to
    688  * process the upload it should always fail on the MHD side.
    689  *
    690  * @param cls argument given together with the function
    691  *        pointer when the handler was registered with MHD
    692  * @param request the request object
    693  * @param path the requested uri (without arguments after "?")
    694  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    695  *        #MHD_HTTP_METHOD_PUT, etc.)
    696  * @param upload_size the size of the message upload content payload,
    697  *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
    698  *                    final chunk has not been processed yet)
    699  * @return action how to proceed, NULL
    700  *         if the request must be aborted due to a serious
    701  *         error while handling the request (implies closure
    702  *         of underling data stream, for HTTP/1.1 it means
    703  *         socket closure).
    704  */
    705 static const struct MHD_Action *
    706 server_upload_req_cb (void *cls,
    707                       struct MHD_Request *MHD_RESTRICT request,
    708                       const struct MHD_String *MHD_RESTRICT path,
    709                       enum MHD_HTTP_Method method,
    710                       uint_fast64_t upload_size)
    711 {
    712   return MHD_action_process_upload (request,
    713                                     1024 * 1024,
    714                                     &uc_fail,
    715                                     NULL,
    716                                     &uc_fail,
    717                                     NULL);
    718 }
    719 
    720 
    721 /**
    722  * A client has requested the given url using the given method
    723  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    724  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).
    725  * If @a upload_size is not zero and response action is provided by this
    726  * callback, then upload will be discarded and the stream (the connection for
    727  * HTTP/1.1) will be closed after sending the response.
    728  *
    729  * Given that we ONLY generate incorrect/malformed requests,
    730  * this function should never be called.
    731  *
    732  * @param cls argument given together with the function
    733  *        pointer when the handler was registered with MHD
    734  * @param request the request object
    735  * @param path the requested uri (without arguments after "?")
    736  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    737  *        #MHD_HTTP_METHOD_PUT, etc.)
    738  * @param upload_size the size of the message upload content payload,
    739  *                    #MHD_SIZE_UNKNOWN for chunked uploads (if the
    740  *                    final chunk has not been processed yet)
    741  * @return action how to proceed, NULL
    742  *         if the request must be aborted due to a serious
    743  *         error while handling the request (implies closure
    744  *         of underling data stream, for HTTP/1.1 it means
    745  *         socket closure).
    746  */
    747 static const struct MHD_Action *
    748 server_req_cb (void *cls,
    749                struct MHD_Request *MHD_RESTRICT request,
    750                const struct MHD_String *MHD_RESTRICT path,
    751                enum MHD_HTTP_Method method,
    752                uint_fast64_t upload_size)
    753 {
    754   fprintf (stderr,
    755            "Test `%s' failed\n",
    756            current->name);
    757   failed = true;
    758   return NULL;
    759 }
    760 
    761 
    762 /**
    763  * Helper function to deal with partial writes.
    764  * Fails hard (calls exit() on failures)!
    765  *
    766  * @param fd where to write to
    767  * @param buf what to write
    768  * @param buf_size number of bytes in @a buf
    769  */
    770 static void
    771 write_all (int fd,
    772            const void *buf,
    773            size_t buf_size)
    774 {
    775   const char *cbuf = buf;
    776   size_t off;
    777 
    778   off = 0;
    779   while (off < buf_size)
    780   {
    781     ssize_t ret;
    782 
    783     ret = write (fd,
    784                  &cbuf[off],
    785                  buf_size - off);
    786     if (ret <= 0)
    787     {
    788       fprintf (stderr,
    789                "Writing %u bytes to %d failed: %s\n",
    790                (unsigned int)(buf_size - off),
    791                fd,
    792                strerror (errno));
    793       exit (1);
    794     }
    795     off += ret;
    796   }
    797 }
    798 
    799 
    800 static int
    801 run_test ()
    802 {
    803   int s;
    804   struct sockaddr_in sa = {
    805     .sin_family = AF_INET,
    806     .sin_port = htons (port),
    807   };
    808   char dummy;
    809 
    810   s = socket (AF_INET, SOCK_STREAM, 0);
    811   if (-1 == s)
    812   {
    813     fprintf (stderr,
    814              "socket() failed: %s\n",
    815              strerror (errno));
    816     return 1;
    817   }
    818   inet_pton (AF_INET,
    819              "127.0.0.1",
    820              &sa.sin_addr);
    821   if (0 != connect (s,
    822                     (struct sockaddr *)&sa,
    823                     sizeof (sa)))
    824   {
    825     fprintf (stderr,
    826              "bind() failed: %s\n",
    827              strerror (errno));
    828     close (s);
    829     return 1;
    830   }
    831   write_all (s,
    832              current->upload,
    833              strlen (current->upload));
    834   shutdown (s,
    835             SHUT_WR);
    836   if (((ssize_t)sizeof (dummy)) !=
    837       read (s,
    838             &dummy,
    839             sizeof (dummy)))
    840   {
    841 #if LOG
    842     fprintf (stderr,
    843              "Server closed connection\n");
    844 #endif
    845   }
    846   close (s);
    847   if (failed)
    848     return 1;
    849   return 0;
    850 }
    851 
    852 
    853 static int
    854 run_tests (struct Test *tests)
    855 {
    856   for (unsigned int i = 0;
    857        NULL != tests[i].name;
    858        i++)
    859   {
    860     current = &tests[i];
    861 #if LOG || 1
    862     fprintf (stderr,
    863              "Running test `%s'\n",
    864              current->name);
    865 #endif
    866     if (0 != run_test ())
    867       return 1;
    868   }
    869   return 0;
    870 }
    871 
    872 
    873 static int
    874 run (MHD_RequestCallback cb,
    875      struct Test *tests,
    876      enum MHD_ProtocolStrictLevel psl)
    877 {
    878   struct MHD_Daemon *d;
    879 
    880   d = MHD_daemon_create (cb,
    881                          NULL);
    882   if (MHD_SC_OK !=
    883       MHD_DAEMON_SET_OPTIONS (
    884         d,
    885         MHD_D_OPTION_WM_WORKER_THREADS (2),
    886 #if !LOG
    887         MHD_D_OPTION_LOG_CALLBACK (NULL,
    888                                    NULL),
    889 #endif
    890         MHD_D_OPTION_PROTOCOL_STRICT_LEVEL (psl,
    891                                             MHD_USL_PRECISE),
    892         MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1000),
    893         MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO,
    894                                 0)))
    895   {
    896     fprintf (stderr,
    897              "Failed to configure daemon!");
    898     return 1;
    899   }
    900 
    901   {
    902     enum MHD_StatusCode sc;
    903 
    904     sc = MHD_daemon_start (d);
    905     if (MHD_SC_OK != sc)
    906     {
    907 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED
    908       fprintf (stderr,
    909                "Failed to start server: %s\n",
    910                MHD_status_code_to_string_lazy (sc));
    911 #else
    912       fprintf (stderr,
    913                "Failed to start server: %u\n",
    914                (unsigned int)sc);
    915 #endif
    916       MHD_daemon_destroy (d);
    917       return 1;
    918     }
    919   }
    920 
    921   {
    922     union MHD_DaemonInfoFixedData info;
    923     enum MHD_StatusCode sc;
    924 
    925     sc = MHD_daemon_get_info_fixed (
    926       d,
    927       MHD_DAEMON_INFO_FIXED_BIND_PORT,
    928       &info);
    929     if (MHD_SC_OK != sc)
    930     {
    931       fprintf (stderr,
    932                "Failed to determine our port: %u\n",
    933                (unsigned int)sc);
    934       MHD_daemon_destroy (d);
    935       return 1;
    936     }
    937     port = info.v_bind_port_uint16;
    938   }
    939 
    940   {
    941     int result;
    942 
    943     result = run_tests (tests);
    944     MHD_daemon_destroy (d);
    945     return result;
    946   }
    947 }
    948 
    949 
    950 int
    951 main (void)
    952 {
    953   if (0 !=
    954       run (&server_req_cb,
    955            tests_must,
    956            MHD_PSL_STRICT))
    957     return 1;
    958   if (0 !=
    959       run (&server_upload_req_cb,
    960            tests_must_upload,
    961            MHD_PSL_STRICT))
    962     return 1;
    963   if (0 !=
    964       run (&server_req_cb,
    965            tests_should,
    966            MHD_PSL_VERY_STRICT))
    967     return 1;
    968   return 0;
    969 }