src/server/test/httpparser.c

Tue, 17 Feb 2026 17:43:10 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 17 Feb 2026 17:43:10 +0100
changeset 678
9908159eff0e
parent 677
3b8d3b37a4d5
permissions
-rw-r--r--

fix http parser error when it ran out of data

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2026 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "httpparser.h"

#include "../daemon/httpparser.h"

#include <cx/string.h>

CX_TEST(test_parse_request_line) {
    CX_TEST_DO {
        HttpParser parser;
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("GET / HTTP/1.1\r\n");
        
        int ret1 = parse_request_line(&parser);
        CX_TEST_ASSERT(!ret1);
        CX_TEST_ASSERT(!cx_strcmp(parser.method, "GET"));
        CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/"));
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1"));
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("POST /longer/url/test/path/ HTTP/1.0\r\n");
        int ret2 = parse_request_line(&parser);
        CX_TEST_ASSERT(!ret2);
        CX_TEST_ASSERT(!cx_strcmp(parser.method, "POST"));
        CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/longer/url/test/path/"));
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.0"));
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("G / v\r\n");
        int ret3 = parse_request_line(&parser);
        CX_TEST_ASSERT(!ret3);
        CX_TEST_ASSERT(!cx_strcmp(parser.method, "G"));
        CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/"));
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "v"));
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HEAD   /space      HTTP/0.9    \r\n");
        int ret4 = parse_request_line(&parser);
        CX_TEST_ASSERT(!ret4);
        CX_TEST_ASSERT(!cx_strcmp(parser.method, "HEAD"));
        CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/space"));
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/0.9"));
        
        // unix-style line break tests
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("PROPFIND /collection/ HTTP/1.1\n");
        int uret1 = parse_request_line(&parser);
        CX_TEST_ASSERT(!uret1);
        CX_TEST_ASSERT(!cx_strcmp(parser.method, "PROPFIND"));
        CX_TEST_ASSERT(!cx_strcmp(parser.uri, "/collection/"));
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1"));
        
        // negative tests
        parser.start_line = cx_mutstr("\r\n");
        int nret1 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret1);
        
        parser.start_line = cx_mutstr("GET\r\n");
        int nret2 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret2);
        
        parser.start_line = cx_mutstr("GET /test\r\n");
        int nret3 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret3);
        
        parser.start_line = cx_mutstr("GET /test   \r\n");
        int nret4 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret4);
        
        parser.start_line = cx_mutstr("/test HTTP/1.1\r\n");
        int nret5 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret5);
        
        parser.start_line = cx_mutstr("GET /uri HTTP/1.1 test\r\n");
        int nret6 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret6);
        
        parser.start_line = cx_mutstr("    /uri2 HTTP/1.1\r\n");
        int nret7 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret7);
        
        parser.start_line = cx_mutstr("GET        HTTP/1.1\r\n");
        int nret8 = parse_request_line(&parser);
        CX_TEST_ASSERT(nret8);
    }
}

CX_TEST(test_parse_response_line) {
    CX_TEST_DO {
        HttpParser parser;
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/1.1 200 OK\r\n");
        parser.type = 1;
        
        int ret1 = parse_response_line(&parser);
        CX_TEST_ASSERT(!ret1);
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.1"));
        CX_TEST_ASSERT(!cx_strcmp(parser.msg, "OK"));
        CX_TEST_ASSERT(parser.status == 200);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/1.0 201 Created\r\n");
        parser.type = 1;
        int ret2 = parse_response_line(&parser);
        CX_TEST_ASSERT(!ret2);
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/1.0"));
        CX_TEST_ASSERT(!cx_strcmp(parser.msg, "Created"));
        CX_TEST_ASSERT(parser.status == 201);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/0.9    204    No Content\r\n");
        parser.type = 1;
        int ret3 = parse_response_line(&parser);
        CX_TEST_ASSERT(!ret3);
        CX_TEST_ASSERT(!cx_strcmp(parser.httpv, "HTTP/0.9"));
        CX_TEST_ASSERT(!cx_strcmp(parser.msg, "No Content"));
        CX_TEST_ASSERT(parser.status == 204);
        
        // negative tests
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/1.1 200\r\n");
        parser.type = 1;
        int nret1 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret1);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("\r\n");
        parser.type = 1;
        int nret2 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret2);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("200\r\n");
        parser.type = 1;
        int nret3 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret3);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/1.1 2345 Test Message\r\n");
        parser.type = 1;
        int nret4 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret4);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("HTTP/1.1 xy1 OK\r\n");
        parser.type = 1;
        int nret5 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret5);
        
        memset(&parser, 0, sizeof(HttpParser));
        parser.start_line = cx_mutstr("   200   OK\r\n");
        parser.type = 1;
        int nret6 = parse_response_line(&parser);
        CX_TEST_ASSERT(nret6);
    }
}

CX_TEST_SUBROUTINE(test_http_parser_process, cxstring request, int chunksize) {
    netbuf buf;
    memset(&buf, 0, sizeof(netbuf));
    buf.inbuf = (unsigned char*)request.ptr;
    buf.maxsize = request.length;
    HeaderArray *header = header_array_create();
    HttpParser *parser = http_parser_new2(0, &buf, header);
    
    while(buf.cursize < request.length) {
        buf.cursize += chunksize;
        if(buf.cursize > buf.maxsize) {
            buf.cursize = buf.maxsize;
        }
        
        int ret = http_parser_process(parser);
        if(buf.cursize < buf.maxsize) {
            CX_TEST_ASSERT(ret == 1);
        } else {
            CX_TEST_ASSERT(ret == 0);
        }
    }
    CX_TEST_ASSERT(http_parser_validate(parser));
    
    http_parser_free(parser);
    header_array_free(header);
}

#define _CX_STR(s) { s, sizeof(s)-1 }

static cxstring req0 = _CX_STR(
    "GET / HTTP/1.1\r\n"
    "Host: example.com\r\n"
    "\r\n");

static cxstring req1 = _CX_STR("GET /api/data?id=42 HTTP/1.1\r\n"
    "Host: api.example.com\r\n"
    "User-Agent: TestClient/1.0\r\n"
    "Accept: application/json\r\n"
    "Accept-Language: en-US,en;q=0.9\r\n"
    "Connection: keep-alive\r\n"
    "Cache-Control: no-cache\r\n"
    "\r\n");

static cxstring req2 = _CX_STR("GET /search?q=network+testing HTTP/1.1\r\n"
    "Host: www.example.com\r\n"
    "User-Agent: Mozilla/5.0 (X11; Linux x86_64) TestBrowser/99.0\r\n"
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n"
    "Accept-Language: en-US,en;q=0.9,de;q=0.8\r\n"
    "Accept-Encoding: gzip, deflate, br\r\n"
    "Connection: keep-alive\r\n"
    "Cache-Control: max-age=0\r\n"
    "Upgrade-Insecure-Requests: 1\r\n"
    "Pragma: no-cache\r\n"
    "DNT: 1\r\n"
    "Sec-Fetch-Dest: document\r\n"
    "Sec-Fetch-Mode: navigate\r\n"
    "Sec-Fetch-Site: none\r\n"
    "Sec-Fetch-User: ?1\r\n"
    "Referer: https://www.example.com/\r\n"
    "X-Forwarded-For: 203.0.113.195\r\n"
    "X-Request-ID: 123e4567-e89b-12d3-a456-426614174000\r\n"
    "\r\n");

CX_TEST(test_http_parser_process_full) {
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, req0.length);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, req1.length);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, req2.length);
    }
}

CX_TEST(test_http_parser_process_chunk_1b) {
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 1);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 1);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 1);
    }
}

CX_TEST(test_http_parser_process_chunk_2b) {
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 2);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 2);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 2);
    }
}

CX_TEST(test_http_parser_process_chunk_3b) {
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 3);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 3);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 3);
    }
}

CX_TEST(test_http_parser_process_chunk_4b) {
    CX_TEST_DO {
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req0, 4);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req1, 4);
        CX_TEST_CALL_SUBROUTINE(test_http_parser_process, req2, 4);
    }
}

mercurial