Skip to content

Instantly share code, notes, and snippets.

@ammarfaizi2
Last active June 3, 2025 16:32
Show Gist options
  • Save ammarfaizi2/eca1255322ddefcbbe2e94982994439f to your computer and use it in GitHub Desktop.
Save ammarfaizi2/eca1255322ddefcbbe2e94982994439f to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: GPL-2.0-only
/*
* gwnet_http1.c - HTTP/1.0 and HTTP/1.1 parser.
*
* Copyright (C) 2025 Ammar Faizi <[email protected]>
*/
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
enum {
GWNET_HTTP_HDR_TYPE_REQ = 0,
GWNET_HTTP_HDR_TYPE_RES = 1,
};
enum {
GWNET_HTTP_HDR_PARSE_ST_INIT = 0,
GWNET_HTTP_HDR_PARSE_ST_FIRST_LINE = 1,
GWNET_HTTP_HDR_PARSE_ST_FIELDS = 2,
GWNET_HTTP_HDR_PARSE_ST_DONE = 3,
};
enum {
GWNET_HTTP_HDR_ERR_NONE = 0,
GWNET_HTTP_HDR_ERR_MALFORMED = 1,
GWNET_HTTP_HDR_ERR_TOO_LONG = 2,
GWNET_HTTP_HDR_ERR_INTERNAL = 100,
};
enum {
GWNET_HTTP_VER_UNKNOWN = 0,
GWNET_HTTP_VER_1_0 = 1,
GWNET_HTTP_VER_1_1 = 2,
};
enum {
GWNET_HTTP_METHOD_UNKNOWN = 0,
GWNET_HTTP_METHOD_GET = 1,
GWNET_HTTP_METHOD_POST = 2,
GWNET_HTTP_METHOD_PUT = 3,
GWNET_HTTP_METHOD_DELETE = 4,
GWNET_HTTP_METHOD_HEAD = 5,
GWNET_HTTP_METHOD_OPTIONS = 6,
GWNET_HTTP_METHOD_PATCH = 7,
GWNET_HTTP_METHOD_TRACE = 8,
GWNET_HTTP_METHOD_CONNECT = 9,
};
struct gwnet_http_hdr_field {
char *key;
char *val;
};
struct gwnet_http_hdr_fields {
struct gwnet_http_hdr_field *ff;
size_t nr;
};
struct gwnet_http_req_hdr {
uint8_t method;
uint8_t version;
char *uri;
char *qs;
struct gwnet_http_hdr_fields fields;
};
struct gwnet_http_res_hdr {
uint8_t version;
uint16_t code;
char *reason;
struct gwnet_http_hdr_fields fields;
};
struct gwnet_http_hdr_pctx {
uint8_t state;
uint8_t err;
uint32_t off;
const char *buf;
uint64_t len;
uint64_t max_len;
};
/**
* Initialize the HTTP header parsing context.
*
* Prepare the given gwnet_http_hdr_pctx structure for use in HTTP
* header parsing operations.
*
* @param ctx Pointer to a gwnet_http_hdr_pctx structure to initialize.
* Must not be NULL.
* @return 0 on success, or a negative value on failure.
*/
int gwnet_http_hdr_pctx_init(struct gwnet_http_hdr_pctx *ctx);
/**
* Free resources associated with the HTTP header parsing context.
*
* This function releases any memory or resources held by the specified
* gwnet_http_hdr_pctx structure. After calling this function, the context
* should not be used unless re-initialized.
*
* @param ctx Pointer to a gwnet_http_hdr_pctx structure to free.
* Must not be NULL.
*/
void gwnet_http_hdr_pctx_free(struct gwnet_http_hdr_pctx *ctx);
/**
* Parses an HTTP request header from the given parsing context.
*
* @param ctx Pointer to the HTTP header parsing context.
* @param hdr Pointer to the structure where the parsed HTTP request
* header will be stored.
* @return 0 on success,
* -EAGAIN if more data is needed,
* -EINVAL if the request line is malformed,
* -ENOMEM if memory allocation fails.
*/
int gwnet_http_req_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_req_hdr *hdr);
/**
* Parses an HTTP response header from the given parsing context.
*
* @param ctx Pointer to the HTTP header parsing context.
* @param hdr Pointer to the structure where the parsed HTTP response
* header will be stored.
* @return 0 on success,
* -EAGAIN if more data is needed,
* -EINVAL if the response line is malformed,
* -ENOMEM if memory allocation fails.
*/
int gwnet_http_res_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_res_hdr *hdr);
void gwnet_http_req_hdr_free(struct gwnet_http_req_hdr *hdr);
void gwnet_http_res_hdr_free(struct gwnet_http_res_hdr *hdr);
/**
* Free all memory associated with the given HTTP header fields
* structure.
*
* @param ff Pointer to the HTTP header fields structure to free.
*/
void gwnet_http_hdr_fields_free(struct gwnet_http_hdr_fields *ff);
/**
* Add a header field with the specified key and value to the HTTP
* header fields structure.
*
* @param ff Pointer to the HTTP header fields structure.
* @param k Null-terminated string containing the header key.
* @param v Null-terminated string containing the header value.
* @return 0 on success, or a negative value on error.
*/
int gwnet_http_hdr_fields_add(struct gwnet_http_hdr_fields *ff, const char *k,
const char *v);
/**
* Add a header field with the specified key and a formatted value to
* the HTTP header fields structure.
*
* @param ff Pointer to the HTTP header fields structure.
* @param k Null-terminated string containing the header key.
* @param fmt printf-style format string for the header value.
* @param ... Arguments for the format string.
* @return 0 on success, or a negative value on error.
*/
__attribute__((__format__(printf, 3, 4)))
int gwnet_http_hdr_fields_addf(struct gwnet_http_hdr_fields *ff,
const char *k, const char *fmt, ...);
/**
* Add a header field with the specified key and value, using explicit
* lengths for both key and value.
*
* @param ff Pointer to the HTTP header fields structure.
* @param k Pointer to the header key.
* @param klen Length of the header key.
* @param v Pointer to the header value.
* @param vlen Length of the header value.
* @return 0 on success, or a negative value on error.
*/
int gwnet_http_hdr_fields_addl(struct gwnet_http_hdr_fields *ff,
const char *k, size_t klen,
const char *v, size_t vlen);
/**
* Retrieve the value of a header field by its key from the HTTP
* header fields structure.
*
* @param ff Pointer to the HTTP header fields structure.
* @param k Null-terminated string containing the header key.
* @return Pointer to the header value, or NULL if not found.
*/
const char *gwnet_http_hdr_fields_get(const struct gwnet_http_hdr_fields *ff,
const char *k);
/**
* Retrieve the value of a header field by its key, using an explicit
* key length, from the HTTP header fields structure.
*
* @param ff Pointer to the HTTP header fields structure.
* @param k Pointer to the header key.
* @param klen Length of the header key.
* @return Pointer to the header value, or NULL if not found.
*/
const char *gwnet_http_hdr_fields_getl(const struct gwnet_http_hdr_fields *ff,
const char *k, size_t klen);
static inline size_t min_st(size_t a, size_t b)
{
return (a < b) ? a : b;
}
/*
* tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-"
* / "." / "^" / "_" / "`" / "|" / "~"
* / DIGIT / ALPHA
* ; any VCHAR, except delimiters
*/
static inline int is_tchar(int c)
{
/* Digits. */
if (c >= '0' && c <= '9')
return 1;
/* Uppercase */
if (c >= 'A' && c <= 'Z')
return 1;
/* Lowercase */
if (c >= 'a' && c <= 'z')
return 1;
/* The 15 extra symbols from the spec */
switch (c) {
case '!': case '#': case '$': case '%': case '&':
case '\'': case '*': case '+': case '-': case '.':
case '^': case '_': case '`': case '|': case '~':
return 1;
default:
return 0;
}
}
static inline int is_space(int c)
{
return (c == ' ' || c == '\t');
}
static inline int is_vchar(int c)
{
/* Visible characters are from 0x20 to 0x7E, inclusive. */
if (c >= 0x20 && c <= 0x7E)
return 1;
/* DEL character is not a visible character. */
if (c == 0x7F)
return 0;
return 0;
}
/**
* Check if the given HTTP header field key is one of the standard
* headers that are allowed to appear multiple times in a message and
* should be merged into a single comma-separated header value
* according to the HTTP specification.
*
* @param key The header field name to check (case-insensitive).
* @param n The length of the header field name.
* @return true if the header is allowed to be provided multiple
* times and should be merged; false otherwise.
*/
static bool is_field_allowed_to_be_duplicate(const char *key, size_t n)
{
static const char *comma_separated_list_headers[] = {
/* RFC 7231, Section 5.3.2 */
"Accept",
/* RFC 7231, Section 5.3.3 */
"Accept-Charset",
/* RFC 7231, Section 5.3.4 */
"Accept-Encoding",
/* RFC 7231, Section 5.3.5 */
"Accept-Language",
/* RFC 7233, Section 2.3 */
"Accept-Ranges",
/* RFC 7231, Section 7.4.1 */
"Allow",
/* RFC 7234, Section 5.2 */
"Cache-Control",
/* RFC 7230, Section 6.1 */
"Connection",
/* RFC 7232, Section 3.1 */
"If-Match",
/* RFC 7232, Section 3.2 */
"If-None-Match",
/* RFC 7233, Section 3.1 */
"Range",
/* RFC 7234, Section 5.4 */
"Pragma",
/* RFC 7235, Section 4.3 */
"Proxy-Authenticate",
/* RFC 7230, Section 4.3 */
"TE",
/* RFC 7230, Section 4.4 */
"Trailer",
/* RFC 7230, Section 3.3.1 */
"Transfer-Encoding",
/* RFC 7230, Section 6.7 */
"Upgrade",
/* RFC 7231, Section 7.1.4 */
"Vary",
/* RFC 7230, Section 5.7.1 */
"Via",
/* RFC 7234, Section 5.5 */
"Warning",
/* RFC 7235, Section 4.1 */
"WWW-Authenticate",
NULL
};
const char **p;
for (p = comma_separated_list_headers; *p; p++) {
const char *hdr = *p;
size_t hdr_len = strlen(hdr);
if (n == hdr_len && !strncasecmp(key, hdr, n))
return true;
}
return false;
}
/**
* Parse the first line of an HTTP/1.x request header, also known as
* the request line. According to RFC 9112, Section 3.1.1, the request
* line consists of the method, request-target, and HTTP version,
* separated by spaces and terminated by CRLF.
*
* Example:
* GET /index.html HTTP/1.1\r\n
*
* It extracts and validates these components from the provided parsing
* context and stores them in the request header structure.
*
* Reference:
* RFC 9112, Section 3:
* https://datatracker.ietf.org/doc/html/rfc9112#section-3
*
* @param ctx Pointer to the HTTP header parsing context.
* @param hdr Pointer to the HTTP request header structure to populate.
* @return 0 on success,
* -EAGAIN if more data is needed,
* -EINVAL if the request line is malformed,
* -ENOMEM if memory allocation fails.
*/
static int parse_hdr_req_first_line(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_req_hdr *hdr)
{
struct method_entry {
const char str[9];
uint8_t len;
uint8_t code;
};
static const struct method_entry methods[] = {
{ "GET", 3, GWNET_HTTP_METHOD_GET },
{ "POST", 4, GWNET_HTTP_METHOD_POST },
{ "PUT", 3, GWNET_HTTP_METHOD_PUT },
{ "DELETE", 6, GWNET_HTTP_METHOD_DELETE },
{ "HEAD", 4, GWNET_HTTP_METHOD_HEAD },
{ "OPTIONS", 7, GWNET_HTTP_METHOD_OPTIONS },
{ "PATCH", 5, GWNET_HTTP_METHOD_PATCH },
{ "TRACE", 5, GWNET_HTTP_METHOD_TRACE },
{ "CONNECT", 7, GWNET_HTTP_METHOD_CONNECT }
};
static const size_t nr_methods = sizeof(methods) / sizeof(methods[0]);
size_t i, cmpl, reml, off = 0, len = ctx->len - ctx->off;
const char *uri, *qs, *buf = &ctx->buf[ctx->off];
uint8_t method_code, version_code;
uint32_t uri_len, qs_len;
method_code = GWNET_HTTP_METHOD_UNKNOWN;
for (i = 0; i < nr_methods; i++) {
const struct method_entry *me = &methods[i];
size_t mlen = me->len;
cmpl = min_st(len, mlen);
if (memcmp(buf, me->str, cmpl))
continue;
if (cmpl < mlen)
return -EAGAIN;
method_code = me->code;
off += mlen;
break;
}
if (method_code == GWNET_HTTP_METHOD_UNKNOWN)
return -EINVAL;
if (off >= len)
return -EAGAIN;
/*
* After the method, there must be a space.
*/
if (!is_space(buf[off]))
return -EINVAL;
/*
* Keep going until we find a non-space character.
*/
while (is_space(buf[off])) {
if (++off >= len)
return -EAGAIN;
}
/*
* Per RFC 7230, Section 5.3.1:
* When making a request directly to an origin server,
* other than a CONNECT or server-wide OPTIONS request
* a client MUST send only the absolute path and query
* components of the target URI as the request-target.
* If the target URI's path component is empty, the
* client MUST send "/" as the path within the
* origin-form of request-target.
*/
if (method_code != GWNET_HTTP_METHOD_CONNECT &&
method_code != GWNET_HTTP_METHOD_OPTIONS) {
if (buf[off] != '/')
return -EINVAL;
} else {
if (!is_vchar(buf[off]))
return -EINVAL;
}
uri = &buf[off];
qs = NULL;
uri_len = 0;
qs_len = 0;
/*
* Keep going until we find a space character.
*/
while (1) {
char c = buf[off++];
if (off >= len)
return -EAGAIN;
if (is_space(c))
break;
if (!is_vchar(c))
return -EINVAL;
uri_len++;
if (qs)
qs_len++;
/*
* If we find a question mark, start assigning the
* the query string.
*/
if (c == '?')
qs = &buf[off];
}
/*
* Keep going until we find a non-space character.
*/
while (is_space(buf[off])) {
if (++off >= len)
return -EAGAIN;
}
/*
* Parse the HTTP version. Only support HTTP/1.0 and HTTP/1.1.
*/
reml = len - off;
cmpl = min_st(reml, 7);
if (memcmp(&buf[off], "HTTP/1.", cmpl))
return -EINVAL;
if (cmpl < 7)
return -EAGAIN;
off += 7;
if (off >= len)
return -EAGAIN;
switch (buf[off]) {
case '0':
version_code = GWNET_HTTP_VER_1_0;
break;
case '1':
version_code = GWNET_HTTP_VER_1_1;
break;
default:
return -EINVAL;
}
if (++off >= len)
return -EAGAIN;
/*
* After the HTTP version, expect a CRLF. But the CR
* is optional, so we can also accept just LF.
*/
if (buf[off] == '\r') {
if (++off >= len)
return -EAGAIN;
}
if (buf[off] != '\n')
return -EINVAL;
++off;
hdr->uri = malloc(uri_len + 1);
if (!hdr->uri)
return -ENOMEM;
if (qs_len) {
hdr->qs = malloc(qs_len + 1);
if (!hdr->qs) {
free(hdr->uri);
hdr->uri = NULL;
return -ENOMEM;
}
memcpy(hdr->qs, qs, qs_len);
hdr->qs[qs_len] = '\0';
}
memcpy(hdr->uri, uri, uri_len);
hdr->uri[uri_len] = '\0';
hdr->method = method_code;
hdr->version = version_code;
ctx->off += off;
return 0;
}
/**
* Parse the first line of an HTTP/1.x response header, also known as
* the status line. According to RFC 7230 Section 3.1.2, the status
* line is formatted as: HTTP-version SP status-code SP reason-phrase
* CRLF.
*
* Example:
* HTTP/1.1 200 OK\r\n
*
* It extracts the HTTP version, status code, and reason phrase from
* the response header's first line and populates the provided
* response header structure.
*
* Reference:
* RFC 7230, Section 3.1.2:
* https://datatracker.ietf.org/doc/html/rfc7230#section-3.1.2
*
* @param ctx Pointer to the HTTP header parsing context.
* @param hdr Pointer to the HTTP response header structure to populate.
* @return 0 on success,
* -EAGAIN if more data is needed,
* -EINVAL if the first line is malformed,
* -ENOMEM if memory allocation fails.
*/
static int parse_hdr_res_first_line(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_res_hdr *hdr)
{
size_t off = 0, len = ctx->len - ctx->off, cmpl, reml, i;
const char *reason, *buf = &ctx->buf[ctx->off], *p;
uint8_t version_code;
uint32_t reason_len;
char rcode[3];
uint16_t code;
if (!len)
return -EAGAIN;
/*
* Parse the HTTP version. Only support HTTP/1.0 and HTTP/1.1.
*/
reml = len - off;
cmpl = min_st(reml, 7);
if (memcmp(buf, "HTTP/1.", cmpl))
return -EINVAL;
if (cmpl < 7)
return -EAGAIN;
off += 7;
if (off >= len)
return -EAGAIN;
switch (buf[off]) {
case '0':
version_code = GWNET_HTTP_VER_1_0;
break;
case '1':
version_code = GWNET_HTTP_VER_1_1;
break;
default:
return -EINVAL;
}
if (++off >= len)
return -EAGAIN;
/*
* After the HTTP version, there must be a space.
*/
if (!is_space(buf[off]))
return -EINVAL;
/*
* Keep going until we find a non-space character.
*/
while (is_space(buf[off])) {
if (++off >= len)
return -EAGAIN;
}
/*
* Parse the HTTP response code. It must be a 3-digit number
* between 100 and 599, inclusive.
*/
rcode[0] = buf[off++];
if (rcode[0] < '1' || rcode[0] > '5')
return -EINVAL;
if (off >= len)
return -EAGAIN;
for (i = 1; i <= 2; i++) {
rcode[i] = buf[off++];
if (rcode[i] < '0' || rcode[i] > '9')
return -EINVAL;
if (off >= len)
return -EAGAIN;
}
code = (rcode[0] - '0') * 100 +
(rcode[1] - '0') * 10 +
(rcode[2] - '0');
/*
* After the response code, there must be a space.
*/
if (!is_space(buf[off]))
return -EINVAL;
/*
* Keep going until we find a non-space character.
*/
while (is_space(buf[off])) {
if (++off >= len)
return -EAGAIN;
}
/*
* After the space, there may be a reason phrase.
* The reason phrase is optional, if it exists,
* it must only contain vchar or space chars.
*
* It ends with a CRLF, but the CR is optional.
*/
reason = &buf[off];
reason_len = 0;
while (1) {
char c = buf[off];
if (c == '\r' || c == '\n')
break;
if (++off >= len)
return -EAGAIN;
if (!is_vchar(c) && !is_space(c))
return -EINVAL;
reason_len++;
}
if (buf[off] == '\r') {
if (++off >= len)
return -EAGAIN;
}
if (buf[off] != '\n')
return -EINVAL;
++off;
if (reason_len) {
/*
* Trim the trailing whitespaces from
* the reason phrase.
*/
p = &reason[reason_len - 1];
while (p >= reason && is_space(*p)) {
--reason_len;
--p;
}
}
hdr->reason = malloc(reason_len + 1);
if (!hdr->reason)
return -ENOMEM;
memcpy(hdr->reason, reason, reason_len);
hdr->reason[reason_len] = '\0';
hdr->version = version_code;
hdr->code = code;
ctx->off += off;
return 0;
}
/**
* Parses HTTP header fields from the provided parsing context.
*
* According to RFC 7230, Section 3.2: "Each header field consists of a
* case-insensitive field name followed by a colon (":"), optional
* whitespace, and the field value." This function processes the header
* fields as described in the RFC.
*
* Reference: RFC 7230, Section 3.2 - Header Fields
* https://datatracker.ietf.org/doc/html/rfc7230#section-3.2
*
* @param ctx Pointer to the HTTP header parsing context.
* @param ff Pointer to the structure where parsed header fields will be
* stored.
* @return 0 on success,
* -EAGAIN if more data is needed,
* -EINVAL if the header fields are malformed,
* -ENOMEM if memory allocation fails.
*/
static int parse_hdr_fields(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_hdr_fields *ff)
{
size_t off = 0, len = ctx->len - ctx->off;
const char *buf = &ctx->buf[ctx->off];
int r;
while (1) {
const char *k, *v, *p;
uint32_t kl, vl;
if (buf[off] == '\r') {
if (++off >= len)
return -EAGAIN;
}
if (buf[off] == '\n') {
++off;
ctx->off += off;
break;
}
/*
* Parse the key. The key must only contain tchar
* characters, and must end with a colon.
*
* After the colon, there may be a space, but it is
* optional. If it exists, it must be followed by
* a vchar or space characters.
*
* The value may contain trailing space characters,
* they must be trimmed.
*
* The value may be empty, but the key must not.
*/
k = &buf[off];
kl = 0;
while (1) {
if (off >= len)
return -EAGAIN;
if (buf[off] == ':')
break;
if (!is_tchar(buf[off]))
return -EINVAL;
kl++;
off++;
}
if (!kl)
return -EINVAL;
if (++off >= len)
return -EAGAIN;
/*
* Keep going until we find a non-space character.
*/
while (is_space(buf[off])) {
if (++off >= len)
return -EAGAIN;
}
v = &buf[off];
vl = 0;
while (1) {
char c = buf[off];
if (c == '\r' || c == '\n')
break;
if (!is_vchar(c) && !is_space(c))
return -EINVAL;
vl++;
off++;
if (off >= len)
return -EAGAIN;
}
if (buf[off] == '\r') {
if (++off >= len)
return -EAGAIN;
}
if (buf[off] != '\n')
return -EINVAL;
++off;
if (vl) {
/*
* Trim trailing whitespaces from the value.
*/
p = &v[vl - 1];
while (p >= v && is_space(*p)) {
--vl;
--p;
}
}
r = gwnet_http_hdr_fields_addl(ff, k, kl, v, vl);
if (r)
return (r < 0) ? r : -EINVAL;
ctx->off += off;
if (off >= len)
return -EAGAIN;
buf = &ctx->buf[ctx->off];
len = ctx->len - ctx->off;
off = 0;
}
return 0;
}
int gwnet_http_hdr_pctx_init(struct gwnet_http_hdr_pctx *ctx)
{
memset(ctx, 0, sizeof(*ctx));
ctx->state = GWNET_HTTP_HDR_PARSE_ST_INIT;
return 0;
}
void gwnet_http_hdr_pctx_free(struct gwnet_http_hdr_pctx *ctx)
{
memset(ctx, 0, sizeof(*ctx));
}
static int __gwnet_http_req_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_req_hdr *hdr)
{
int r = 0;
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_INIT) {
ctx->state = GWNET_HTTP_HDR_PARSE_ST_FIRST_LINE;
memset(hdr, 0, sizeof(*hdr));
}
if (!ctx->len)
return -EAGAIN;
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_FIRST_LINE) {
r = parse_hdr_req_first_line(ctx, hdr);
if (r)
return r;
ctx->state = GWNET_HTTP_HDR_PARSE_ST_FIELDS;
}
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_FIELDS) {
r = parse_hdr_fields(ctx, &hdr->fields);
if (r)
return r;
ctx->state = GWNET_HTTP_HDR_PARSE_ST_DONE;
}
return r;
}
static int __gwnet_http_res_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_res_hdr *hdr)
{
int r = 0;
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_INIT) {
ctx->state = GWNET_HTTP_HDR_PARSE_ST_FIRST_LINE;
memset(hdr, 0, sizeof(*hdr));
}
if (!ctx->len)
return -EAGAIN;
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_FIRST_LINE) {
r = parse_hdr_res_first_line(ctx, hdr);
if (r)
return r;
ctx->state = GWNET_HTTP_HDR_PARSE_ST_FIELDS;
}
if (ctx->state == GWNET_HTTP_HDR_PARSE_ST_FIELDS) {
r = parse_hdr_fields(ctx, &hdr->fields);
if (r)
return r;
ctx->state = GWNET_HTTP_HDR_PARSE_ST_DONE;
}
return r;
}
static int translate_err(struct gwnet_http_hdr_pctx *ctx, int r)
{
switch (r) {
case -EAGAIN:
ctx->err = GWNET_HTTP_HDR_ERR_NONE;
break;
case -EINVAL:
ctx->err = GWNET_HTTP_HDR_ERR_MALFORMED;
break;
default:
ctx->err = GWNET_HTTP_HDR_ERR_INTERNAL;
break;
}
return r;
}
int gwnet_http_req_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_req_hdr *hdr)
{
int r = __gwnet_http_req_hdr_parse(ctx, hdr);
if (!r)
return r;
return translate_err(ctx, r);
}
int gwnet_http_res_hdr_parse(struct gwnet_http_hdr_pctx *ctx,
struct gwnet_http_res_hdr *hdr)
{
int r = __gwnet_http_res_hdr_parse(ctx, hdr);
if (!r)
return r;
return translate_err(ctx, r);
}
void gwnet_http_req_hdr_free(struct gwnet_http_req_hdr *hdr)
{
if (!hdr)
return;
free(hdr->uri);
free(hdr->qs);
gwnet_http_hdr_fields_free(&hdr->fields);
memset(hdr, 0, sizeof(*hdr));
}
void gwnet_http_res_hdr_free(struct gwnet_http_res_hdr *hdr)
{
if (!hdr)
return;
free(hdr->reason);
gwnet_http_hdr_fields_free(&hdr->fields);
memset(hdr, 0, sizeof(*hdr));
}
void gwnet_http_hdr_fields_free(struct gwnet_http_hdr_fields *ff)
{
size_t i;
if (!ff)
return;
for (i = 0; i < ff->nr; i++) {
free(ff->ff[i].key);
free(ff->ff[i].val);
}
free(ff->ff);
memset(ff, 0, sizeof(*ff));
}
int gwnet_http_hdr_fields_add(struct gwnet_http_hdr_fields *ff, const char *k,
const char *v)
{
return gwnet_http_hdr_fields_addl(ff, k, strlen(k), v, strlen(v));
}
int gwnet_http_hdr_fields_addf(struct gwnet_http_hdr_fields *ff,
const char *k, const char *fmt, ...)
{
va_list args1, args2;
size_t vlen;
char *v;
int r;
va_start(args1, fmt);
va_copy(args2, args1);
r = vsnprintf(NULL, 0, fmt, args1);
va_end(args1);
v = malloc(r + 1);
if (!v) {
r = -ENOMEM;
goto out;
}
vlen = (size_t)r;
vsnprintf(v, vlen + 1, fmt, args2);
r = gwnet_http_hdr_fields_addl(ff, k, strlen(k), v, vlen);
free(v);
out:
va_end(args2);
return r;
}
static ssize_t find_hdr_field_idx(const struct gwnet_http_hdr_fields *ff,
const char *k, size_t klen)
{
size_t i;
for (i = 0; i < ff->nr; i++) {
struct gwnet_http_hdr_field *f = &ff->ff[i];
if (!strncasecmp(f->key, k, klen)) {
if (strlen(f->key) == klen)
return i;
}
}
return -ENOENT;
}
int gwnet_http_hdr_fields_addl(struct gwnet_http_hdr_fields *ff,
const char *k, size_t klen,
const char *v, size_t vlen)
{
ssize_t idx = find_hdr_field_idx(ff, k, klen);
struct gwnet_http_hdr_field *f;
char *new_val;
if (idx < 0) {
struct gwnet_http_hdr_field *new_fields;
size_t new_size;
char *kc, *vc;
kc = malloc(klen + 1);
if (!kc)
return -ENOMEM;
vc = malloc(vlen + 1);
if (!vc) {
free(kc);
return -ENOMEM;
}
new_size = (ff->nr + 1) * sizeof(*ff->ff);
new_fields = realloc(ff->ff, new_size);
if (!new_fields) {
free(kc);
free(vc);
return -ENOMEM;
}
memcpy(kc, k, klen);
memcpy(vc, v, vlen);
kc[klen] = '\0';
vc[vlen] = '\0';
ff->ff = new_fields;
f = &ff->ff[ff->nr++];
f->key = kc;
f->val = vc;
return 0;
}
f = &ff->ff[idx];
if (is_field_allowed_to_be_duplicate(k, klen)) {
size_t cur_len, new_val_len;
if (!vlen)
return 0;
cur_len = strlen(f->val);
new_val_len = cur_len + vlen + 3;
new_val = realloc(f->val, new_val_len);
if (!new_val)
return -ENOMEM;
if (!cur_len) {
memcpy(new_val, v, vlen);
new_val[vlen] = '\0';
} else {
memcpy(&new_val[cur_len], ", ", 2);
memcpy(&new_val[cur_len + 2], v, vlen);
new_val[cur_len + 2 + vlen] = '\0';
}
f->val = new_val;
return 0;
} else {
new_val = realloc(f->val, vlen + 1);
if (!new_val)
return -ENOMEM;
memcpy(new_val, v, vlen);
new_val[vlen] = '\0';
f->val = new_val;
return EEXIST;
}
}
const char *gwnet_http_hdr_fields_get(const struct gwnet_http_hdr_fields *ff,
const char *k)
{
return gwnet_http_hdr_fields_getl(ff, k, strlen(k));
}
const char *gwnet_http_hdr_fields_getl(const struct gwnet_http_hdr_fields *ff,
const char *k, size_t klen)
{
ssize_t idx = find_hdr_field_idx(ff, k, klen);
if (idx < 0)
return NULL;
return ff->ff[idx].val;
}
#ifdef GWNET_HTTP1_TESTS
#define PRTEST_OK() \
do { \
static bool __printed; \
if (!__printed) { \
printf("Test passed: %s\n", __func__); \
__printed = true; \
} \
} while (0)
#define ASSERT_HDRF(f, k, v) \
do { \
assert(!strcmp((f)->key, k)); \
assert(!strcmp((f)->val, v)); \
} while (0)
static void test_req_hdr_simple(void)
{
static const char buf[] =
"GET /index.html HTTP/1.0\r\n"
"Host: example.com\r\n"
"User-Agent: gwhttp\r\n"
"Accept: */*\r\n"
"Connection: keep-alive\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_0);
assert(!strcmp(hdr.uri, "/index.html"));
assert(!hdr.qs);
assert(hdr.fields.nr == 4);
ASSERT_HDRF(&hdr.fields.ff[0], "Host", "example.com");
ASSERT_HDRF(&hdr.fields.ff[1], "User-Agent", "gwhttp");
ASSERT_HDRF(&hdr.fields.ff[2], "Accept", "*/*");
ASSERT_HDRF(&hdr.fields.ff[3], "Connection", "keep-alive");
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_simple(void)
{
static const char buf[] =
"HTTP/1.0 200 OK\r\n"
"Server: gwhttpd\r\n"
"Date: Mon, 01 Jan 1999 00:00:00 GMT\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Content-Length: 1234\r\n"
"Connection: close\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(ctx.err == GWNET_HTTP_HDR_ERR_NONE);
assert(hdr.code == 200);
assert(!strcmp(hdr.reason, "OK"));
assert(hdr.version == GWNET_HTTP_VER_1_0);
assert(hdr.fields.nr == 5);
ASSERT_HDRF(&hdr.fields.ff[0], "Server", "gwhttpd");
ASSERT_HDRF(&hdr.fields.ff[1], "Date", "Mon, 01 Jan 1999 00:00:00 GMT");
ASSERT_HDRF(&hdr.fields.ff[2], "Content-Type", "text/html; charset=UTF-8");
ASSERT_HDRF(&hdr.fields.ff[3], "Content-Length", "1234");
ASSERT_HDRF(&hdr.fields.ff[4], "Connection", "close");
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_query_string(void)
{
static const char buf[] =
"GET /index.html?foo=bar&baz=qux HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(!strcmp(hdr.uri, "/index.html?foo=bar&baz=qux"));
assert(!strcmp(hdr.qs, "foo=bar&baz=qux"));
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_query_string_empty(void)
{
static const char buf[] =
"GET /index.html? HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(!strcmp(hdr.uri, "/index.html?"));
assert(!hdr.qs);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_uri_chars(void)
{
static const char buf[] =
"GET AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_uri_chars2(void)
{
static const char buf[] =
"GET /\0\1\2\3\4\5\6\7 HTTP/1.1\r\n"
"Host: example.com\r\n"
"Invalid-Header: \r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_method(void)
{
static const char buf[] =
"INVALID /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_version(void)
{
static const char buf[] =
"GET /index.html HTTP/2.0\r\n"
"Host: example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_invalid_version(void)
{
static const char buf[] =
"HTTP/2.0 200 OK\r\n"
"Server: gwhttpd\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_trim_whitespaces(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host:\t\t\t\t\t\t\t\t\texample.com\t\t\t\t\t\t\r\n"
"User-Agent: gwhttp \t \t \t \r\n"
"Accept:*/* \t\t\t \t\r\n"
"Connection: \t\t\t \t\tkeep-alive\t\t \t \r\n"
"X-Test-A:AAAA\r\n"
"X-Test-B: BBBB\r\n"
"X-Test-C:CCCC \t\t\t\t \r\n"
"X-Test-D: DDDD \r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(!strcmp(hdr.uri, "/index.html"));
assert(!hdr.qs);
assert(hdr.fields.nr == 8);
ASSERT_HDRF(&hdr.fields.ff[0], "Host", "example.com");
ASSERT_HDRF(&hdr.fields.ff[1], "User-Agent", "gwhttp");
ASSERT_HDRF(&hdr.fields.ff[2], "Accept", "*/*");
ASSERT_HDRF(&hdr.fields.ff[3], "Connection", "keep-alive");
ASSERT_HDRF(&hdr.fields.ff[4], "X-Test-A", "AAAA");
ASSERT_HDRF(&hdr.fields.ff[5], "X-Test-B", "BBBB");
ASSERT_HDRF(&hdr.fields.ff[6], "X-Test-C", "CCCC");
ASSERT_HDRF(&hdr.fields.ff[7], "X-Test-D", "DDDD");
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_trim_whitespaces(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: \t\t\t\t\t\tgwhttpd\r\n"
"Date: \t\t\t\tMon, 01 Jan 1999 00:00:00 GMT\r\n"
"Content-Type: \ttext/html; charset=UTF-8\r\n"
"Content-Length: \t1234\r\n"
"Connection: \tclose\r\n"
"X-Test-A:AAAA\r\n"
"X-Test-B: BBBB\r\n"
"X-Test-C:CCCC \r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(ctx.err == GWNET_HTTP_HDR_ERR_NONE);
assert(hdr.code == 200);
assert(!strcmp(hdr.reason, "OK"));
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(hdr.fields.nr == 8);
ASSERT_HDRF(&hdr.fields.ff[0], "Server", "gwhttpd");
ASSERT_HDRF(&hdr.fields.ff[1], "Date", "Mon, 01 Jan 1999 00:00:00 GMT");
ASSERT_HDRF(&hdr.fields.ff[2], "Content-Type", "text/html; charset=UTF-8");
ASSERT_HDRF(&hdr.fields.ff[3], "Content-Length", "1234");
ASSERT_HDRF(&hdr.fields.ff[4], "Connection", "close");
ASSERT_HDRF(&hdr.fields.ff[5], "X-Test-A", "AAAA");
ASSERT_HDRF(&hdr.fields.ff[6], "X-Test-B", "BBBB");
ASSERT_HDRF(&hdr.fields.ff[7], "X-Test-C", "CCCC");
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_duplicate_fields(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"Host: example.org\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_invalid_duplicate_fields(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: gwhttpd\r\n"
"Server: gwhttpd2\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_space_before_colon(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host : example.com\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_invalid_space_before_colon(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server : gwhttpd\r\n"
"Date : Mon, 01 Jan 1999 00:00:00 GMT\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_duplicate_fields_merged_into_comma(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com, example.org\r\n"
"User-Agent: gwhttp\r\n"
"Accept: application/json\r\n"
"Accept: text/html\r\n"
"Accept: plain/text\r\n"
"Connection: keep-alive\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(!strcmp(hdr.uri, "/index.html"));
assert(!hdr.qs);
assert(hdr.fields.nr == 4);
ASSERT_HDRF(&hdr.fields.ff[0], "Host", "example.com, example.org");
ASSERT_HDRF(&hdr.fields.ff[1], "User-Agent", "gwhttp");
ASSERT_HDRF(&hdr.fields.ff[2], "Accept", "application/json, text/html, plain/text");
ASSERT_HDRF(&hdr.fields.ff[3], "Connection", "keep-alive");
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_duplicate_fields_merged_into_comma(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: gwhttpd, gwhttpd2\r\n"
"Date: Mon, 01 Jan 1999 00:00:00 GMT\r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Transfer-Encoding: gzip\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Length: 1234\r\n"
"Connection: close\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(ctx.err == GWNET_HTTP_HDR_ERR_NONE);
assert(hdr.code == 200);
assert(!strcmp(hdr.reason, "OK"));
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(hdr.fields.nr == 6);
ASSERT_HDRF(&hdr.fields.ff[0], "Server", "gwhttpd, gwhttpd2");
ASSERT_HDRF(&hdr.fields.ff[1], "Date", "Mon, 01 Jan 1999 00:00:00 GMT");
ASSERT_HDRF(&hdr.fields.ff[2], "Content-Type", "text/html; charset=UTF-8");
ASSERT_HDRF(&hdr.fields.ff[3], "Transfer-Encoding", "gzip, chunked");
ASSERT_HDRF(&hdr.fields.ff[4], "Content-Length", "1234");
ASSERT_HDRF(&hdr.fields.ff[5], "Connection", "close");
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_duplicate_fields_empty_value(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"User-Agent: \r\n"
"Accept: \r\n"
"Accept: \r\n"
"Accept: text/html\r\n"
"Accept: \r\n"
"Accept: \r\n"
"Accept: application/json\r\n"
"Accept: \r\n"
"Accept: \r\n"
"Accept: plain/text\r\n"
"Connection: keep-alive\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(ctx.err == GWNET_HTTP_HDR_ERR_NONE);
assert(hdr.method == GWNET_HTTP_METHOD_GET);
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(!strcmp(hdr.uri, "/index.html"));
assert(!hdr.qs);
assert(hdr.fields.nr == 4);
ASSERT_HDRF(&hdr.fields.ff[0], "Host", "example.com");
ASSERT_HDRF(&hdr.fields.ff[1], "User-Agent","");
ASSERT_HDRF(&hdr.fields.ff[2], "Accept", "text/html, application/json, plain/text");
ASSERT_HDRF(&hdr.fields.ff[3], "Connection", "keep-alive");
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_duplicate_fields_empty_value(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: gwhttpd\r\n"
"Date: \r\n"
"Content-Type: text/html; charset=UTF-8\r\n"
"Transfer-Encoding: \r\n"
"Transfer-Encoding: gzip\r\n"
"Transfer-Encoding: chunked\r\n"
"Content-Length: 1234\r\n"
"Connection: close\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(!r);
assert(ctx.off == len);
assert(ctx.state == GWNET_HTTP_HDR_PARSE_ST_DONE);
assert(ctx.err == GWNET_HTTP_HDR_ERR_NONE);
assert(hdr.code == 200);
assert(!strcmp(hdr.reason, "OK"));
assert(hdr.version == GWNET_HTTP_VER_1_1);
assert(hdr.fields.nr == 6);
ASSERT_HDRF(&hdr.fields.ff[0], "Server", "gwhttpd");
ASSERT_HDRF(&hdr.fields.ff[1], "Date", "");
ASSERT_HDRF(&hdr.fields.ff[2], "Content-Type", "text/html; charset=UTF-8");
ASSERT_HDRF(&hdr.fields.ff[3], "Transfer-Encoding", "gzip, chunked");
ASSERT_HDRF(&hdr.fields.ff[4], "Content-Length", "1234");
ASSERT_HDRF(&hdr.fields.ff[5], "Connection", "close");
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_field_val_chars(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"Invalid-Header: \x01\x02\x03\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_invalid_field_val_chars(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: gwhttpd\r\n"
"Invalid-Header: \x01\x02\x03\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_req_hdr_invalid_field_key_chars(void)
{
static const char buf[] =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n"
"Invalid-Header\x01\x02\x03: value\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_req_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_req_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_req_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
static void test_res_hdr_invalid_field_key_chars(void)
{
static const char buf[] =
"HTTP/1.1 200 OK\r\n"
"Server: gwhttpd\r\n"
"Invalid-Header\x01\x02\x03: value\r\n"
"\r\n";
static const size_t len = sizeof(buf) - 1;
struct gwnet_http_hdr_pctx ctx;
struct gwnet_http_res_hdr hdr;
int r;
r = gwnet_http_hdr_pctx_init(&ctx);
assert(!r);
ctx.buf = buf;
ctx.len = len;
r = gwnet_http_res_hdr_parse(&ctx, &hdr);
assert(r == -EINVAL);
assert(ctx.err == GWNET_HTTP_HDR_ERR_MALFORMED);
gwnet_http_res_hdr_free(&hdr);
gwnet_http_hdr_pctx_free(&ctx);
PRTEST_OK();
}
int main(void)
{
size_t i;
for (i = 0; i < 5; i++) {
test_req_hdr_simple();
test_res_hdr_simple();
test_req_hdr_query_string();
test_req_hdr_query_string_empty();
test_req_hdr_invalid_uri_chars();
test_req_hdr_invalid_uri_chars2();
test_req_hdr_invalid_method();
test_req_hdr_invalid_version();
test_res_hdr_invalid_version();
test_req_hdr_trim_whitespaces();
test_res_hdr_trim_whitespaces();
test_req_hdr_invalid_duplicate_fields();
test_res_hdr_invalid_duplicate_fields();
test_req_hdr_invalid_space_before_colon();
test_res_hdr_invalid_space_before_colon();
test_req_hdr_duplicate_fields_merged_into_comma();
test_res_hdr_duplicate_fields_merged_into_comma();
test_req_hdr_duplicate_fields_empty_value();
test_res_hdr_duplicate_fields_empty_value();
test_req_hdr_invalid_field_val_chars();
test_res_hdr_invalid_field_val_chars();
test_req_hdr_invalid_field_key_chars();
test_res_hdr_invalid_field_key_chars();
}
printf("All tests passed!\n");
return 0;
}
#endif /* #ifdef GWNET_HTTP1_TESTS */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment