UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2026 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "httpparser.h" 30 31 #include "../daemon/httpparser.h" 32 33 #include <cx/string.h> 34 35 CX_TEST(test_parse_request_line) { 36 CX_TEST_DO { 37 HttpParser parser; 38 memset(&parser, 0, sizeof(HttpParser)); 39 parser.start_line = cx_mutstr("GET / HTTP/1.1\r\n"); 40 41 int ret1 = parse_request_line(&parser); 42 CX_TEST_ASSERT(!ret1); 43 CX_TEST_ASSERT(!cx_strcmp(parser.method, "GET")); 44 CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/")); 45 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1")); 46 47 memset(&parser, 0, sizeof(HttpParser)); 48 parser.start_line = cx_mutstr("POST /longer/url/test/path/ HTTP/1.0\r\n"); 49 int ret2 = parse_request_line(&parser); 50 CX_TEST_ASSERT(!ret2); 51 CX_TEST_ASSERT(!cx_strcmp(parser.method, "POST")); 52 CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/longer/url/test/path/")); 53 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.0")); 54 55 memset(&parser, 0, sizeof(HttpParser)); 56 parser.start_line = cx_mutstr("G / v\r\n"); 57 int ret3 = parse_request_line(&parser); 58 CX_TEST_ASSERT(!ret3); 59 CX_TEST_ASSERT(!cx_strcmp(parser.method, "G")); 60 CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/")); 61 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "v")); 62 63 memset(&parser, 0, sizeof(HttpParser)); 64 parser.start_line = cx_mutstr("HEAD /space HTTP/0.9 \r\n"); 65 int ret4 = parse_request_line(&parser); 66 CX_TEST_ASSERT(!ret4); 67 CX_TEST_ASSERT(!cx_strcmp(parser.method, "HEAD")); 68 CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/space")); 69 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/0.9")); 70 71 // unix-style line break tests 72 memset(&parser, 0, sizeof(HttpParser)); 73 parser.start_line = cx_mutstr("PROPFIND /collection/ HTTP/1.1\n"); 74 int uret1 = parse_request_line(&parser); 75 CX_TEST_ASSERT(!uret1); 76 CX_TEST_ASSERT(!cx_strcmp(parser.method, "PROPFIND")); 77 CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/collection/")); 78 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1")); 79 80 // negative tests 81 parser.start_line = cx_mutstr("\r\n"); 82 int nret1 = parse_request_line(&parser); 83 CX_TEST_ASSERT(nret1); 84 85 parser.start_line = cx_mutstr("GET\r\n"); 86 int nret2 = parse_request_line(&parser); 87 CX_TEST_ASSERT(nret2); 88 89 parser.start_line = cx_mutstr("GET /test\r\n"); 90 int nret3 = parse_request_line(&parser); 91 CX_TEST_ASSERT(nret3); 92 93 parser.start_line = cx_mutstr("GET /test \r\n"); 94 int nret4 = parse_request_line(&parser); 95 CX_TEST_ASSERT(nret4); 96 97 parser.start_line = cx_mutstr("/test HTTP/1.1\r\n"); 98 int nret5 = parse_request_line(&parser); 99 CX_TEST_ASSERT(nret5); 100 101 parser.start_line = cx_mutstr("GET /uri HTTP/1.1 test\r\n"); 102 int nret6 = parse_request_line(&parser); 103 CX_TEST_ASSERT(nret6); 104 105 parser.start_line = cx_mutstr(" /uri2 HTTP/1.1\r\n"); 106 int nret7 = parse_request_line(&parser); 107 CX_TEST_ASSERT(nret7); 108 109 parser.start_line = cx_mutstr("GET HTTP/1.1\r\n"); 110 int nret8 = parse_request_line(&parser); 111 CX_TEST_ASSERT(nret8); 112 } 113 } 114 115 CX_TEST(test_parse_response_line) { 116 CX_TEST_DO { 117 HttpParser parser; 118 memset(&parser, 0, sizeof(HttpParser)); 119 parser.start_line = cx_mutstr("HTTP/1.1 200 OK\r\n"); 120 parser.type = 1; 121 122 int ret1 = parse_response_line(&parser); 123 CX_TEST_ASSERT(!ret1); 124 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1")); 125 CX_TEST_ASSERT(!cx_strcmp(parser.msg, "OK")); 126 CX_TEST_ASSERT(parser.status == 200); 127 128 memset(&parser, 0, sizeof(HttpParser)); 129 parser.start_line = cx_mutstr("HTTP/1.0 201 Created\r\n"); 130 parser.type = 1; 131 int ret2 = parse_response_line(&parser); 132 CX_TEST_ASSERT(!ret2); 133 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.0")); 134 CX_TEST_ASSERT(!cx_strcmp(parser.msg, "Created")); 135 CX_TEST_ASSERT(parser.status == 201); 136 137 memset(&parser, 0, sizeof(HttpParser)); 138 parser.start_line = cx_mutstr("HTTP/0.9 204 No Content\r\n"); 139 parser.type = 1; 140 int ret3 = parse_response_line(&parser); 141 CX_TEST_ASSERT(!ret3); 142 CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/0.9")); 143 CX_TEST_ASSERT(!cx_strcmp(parser.msg, "No Content")); 144 CX_TEST_ASSERT(parser.status == 204); 145 146 // negative tests 147 memset(&parser, 0, sizeof(HttpParser)); 148 parser.start_line = cx_mutstr("HTTP/1.1 200\r\n"); 149 parser.type = 1; 150 int nret1 = parse_response_line(&parser); 151 CX_TEST_ASSERT(nret1); 152 153 memset(&parser, 0, sizeof(HttpParser)); 154 parser.start_line = cx_mutstr("\r\n"); 155 parser.type = 1; 156 int nret2 = parse_response_line(&parser); 157 CX_TEST_ASSERT(nret2); 158 159 memset(&parser, 0, sizeof(HttpParser)); 160 parser.start_line = cx_mutstr("200\r\n"); 161 parser.type = 1; 162 int nret3 = parse_response_line(&parser); 163 CX_TEST_ASSERT(nret3); 164 165 memset(&parser, 0, sizeof(HttpParser)); 166 parser.start_line = cx_mutstr("HTTP/1.1 2345 Test Message\r\n"); 167 parser.type = 1; 168 int nret4 = parse_response_line(&parser); 169 CX_TEST_ASSERT(nret4); 170 171 memset(&parser, 0, sizeof(HttpParser)); 172 parser.start_line = cx_mutstr("HTTP/1.1 xy1 OK\r\n"); 173 parser.type = 1; 174 int nret5 = parse_response_line(&parser); 175 CX_TEST_ASSERT(nret5); 176 177 memset(&parser, 0, sizeof(HttpParser)); 178 parser.start_line = cx_mutstr(" 200 OK\r\n"); 179 parser.type = 1; 180 int nret6 = parse_response_line(&parser); 181 CX_TEST_ASSERT(nret6); 182 } 183 } 184 185 CX_TEST_SUBROUTINE(test_http_parser_process, cxstring request, int chunksize) { 186 netbuf buf; 187 memset(&buf, 0, sizeof(netbuf)); 188 buf.inbuf = (unsigned char*)request.ptr; 189 buf.maxsize = request.length; 190 HeaderArray *header = header_array_create(); 191 HttpParser *parser = http_parser_new2(0, &buf, header); 192 193 while(buf.cursize < request.length) { 194 buf.cursize += chunksize; 195 if(buf.cursize > buf.maxsize) { 196 buf.cursize = buf.maxsize; 197 } 198 199 int ret = http_parser_process(parser); 200 if(buf.cursize < buf.maxsize) { 201 CX_TEST_ASSERT(ret == 1); 202 } else { 203 CX_TEST_ASSERT(ret == 0); 204 } 205 } 206 CX_TEST_ASSERT(http_parser_validate(parser)); 207 208 http_parser_free(parser); 209 header_array_free(header); 210 } 211 212 #define _CX_STR(s) { s, sizeof(s)-1 } 213 214 static cxstring req0 = _CX_STR( 215 "GET / HTTP/1.1\r\n" 216 "Host: example.com\r\n" 217 "\r\n"); 218 219 static cxstring req1 = _CX_STR("GET /api/data?id=42 HTTP/1.1\r\n" 220 "Host: api.example.com\r\n" 221 "User-Agent: TestClient/1.0\r\n" 222 "Accept: application/json\r\n" 223 "Accept-Language: en-US,en;q=0.9\r\n" 224 "Connection: keep-alive\r\n" 225 "Cache-Control: no-cache\r\n" 226 "\r\n"); 227 228 static cxstring req2 = _CX_STR("GET /search?q=network+testing HTTP/1.1\r\n" 229 "Host: www.example.com\r\n" 230 "User-Agent: Mozilla/5.0 (X11; Linux x86_64) TestBrowser/99.0\r\n" 231 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n" 232 "Accept-Language: en-US,en;q=0.9,de;q=0.8\r\n" 233 "Accept-Encoding: gzip, deflate, br\r\n" 234 "Connection: keep-alive\r\n" 235 "Cache-Control: max-age=0\r\n" 236 "Upgrade-Insecure-Requests: 1\r\n" 237 "Pragma: no-cache\r\n" 238 "DNT: 1\r\n" 239 "Sec-Fetch-Dest: document\r\n" 240 "Sec-Fetch-Mode: navigate\r\n" 241 "Sec-Fetch-Site: none\r\n" 242 "Sec-Fetch-User: ?1\r\n" 243 "Referer: https://www.example.com/\r\n" 244 "X-Forwarded-For: 203.0.113.195\r\n" 245 "X-Request-ID: 123e4567-e89b-12d3-a456-426614174000\r\n" 246 "\r\n"); 247 248 CX_TEST(test_http_parser_process_full) { 249 CX_TEST_DO { 250 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, req0.length); 251 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, req1.length); 252 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, req2.length); 253 } 254 } 255 256 CX_TEST(test_http_parser_process_chunk_1b) { 257 CX_TEST_DO { 258 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 1); 259 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 1); 260 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 1); 261 } 262 } 263 264 CX_TEST(test_http_parser_process_chunk_2b) { 265 CX_TEST_DO { 266 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 2); 267 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 2); 268 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 2); 269 } 270 } 271 272 CX_TEST(test_http_parser_process_chunk_3b) { 273 CX_TEST_DO { 274 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 3); 275 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 3); 276 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 3); 277 } 278 } 279 280 CX_TEST(test_http_parser_process_chunk_4b) { 281 CX_TEST_DO { 282 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 4); 283 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 4); 284 CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 4); 285 } 286 } 287