Support URL parameters

Now, http_payload includes a list of human-readable parameters that can
be read (but not modified) by users. Given the following example link:

/test?key1=value1&key2=value2

This will generate two parameters, with the following values:

{
	.args =
	{
		[0] = {.key = "key1", .value = "value1"},
		[1] = {.key = "key2", .value = "value2"}
	},

	.n_args = 2
}

As expected, if any URL parameters are given, struct http_payload member
"resource" is accordingly trimmed so as not to include any parameters.
Therefore, considering the example above:

{.args = {...}, .resource = "/test"}

Limitations:

- Since the definition of struct http_arg is both shared by http.h
(as a read-only pointer within struct http_payload) and http.c
(as a read/write pointer within struct ctx), its members (namely key
and value) must remain as read/write pointers, even if they must not
be modified by users of http.h.
This commit is contained in:
Xavier Del Campo Romero 2023-04-23 05:09:53 +02:00
parent 2e2c9b4c93
commit 011807e1d6
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
2 changed files with 241 additions and 19 deletions

253
http.c
View File

@ -84,6 +84,9 @@ struct http_ctx
} *forms;
} mf;
} u;
struct http_arg *args;
size_t n_args;
} ctx;
struct write_ctx
@ -126,6 +129,218 @@ static int get_version(struct http_ctx *const h, const char *const v)
return -1;
}
static void arg_free(struct http_arg *const a)
{
if (a)
{
free(a->key);
free(a->value);
}
}
static size_t chrcnt(const char *s, const int c)
{
size_t ret = 0;
while (*s++ == c)
ret++;
return ret;
}
static void arg_decode(char *s)
{
while ((s = strchr(s, '+')))
*s = ' ';
}
static int parse_arg(struct ctx *const c, const char *const arg,
const size_t n)
{
int ret = -1;
struct http_arg a = {0}, *args = NULL;
const char *sep = memchr(arg, '=', n);
if (!sep)
{
fprintf(stderr, "%s: expected '='\n", __func__);
ret = 1;
goto end;
}
else if (sep == arg)
{
fprintf(stderr, "%s: expected key\n", __func__);
ret = 1;
goto end;
}
const char *const value = sep + 1;
if (!*value)
{
fprintf(stderr, "%s: missing value: %.*s\n", __func__, (int)n, arg);
ret = 1;
goto end;
}
const size_t keylen = sep - arg, valuelen = n - keylen - 1;
a = (const struct http_arg)
{
.key = strndup(arg, keylen),
.value = strndup(value, valuelen)
};
if (!a.key || !a.value)
{
fprintf(stderr, "%s: strndup(3) key: %s\n", __func__, strerror(errno));
goto end;
}
else if (!(args = realloc(c->args, (c->n_args + 1) * sizeof *args)))
{
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
goto end;
}
arg_decode(a.key);
arg_decode(a.value);
args[c->n_args++] = a;
c->args = args;
ret = 0;
end:
if (ret)
arg_free(&a);
return ret;
}
static int parse_first_arg(struct ctx *const c, const char *const arg,
const char *const ad_arg, const char *const res)
{
int error;
const char *const next = arg + 1;
if (chrcnt(next, '?'))
{
fprintf(stderr, "%s: more than one argument indicator '?' found: %s\n",
__func__, res);
return 1;
}
const size_t n = ad_arg ? ad_arg - next : strlen(next);
if (!n)
{
fprintf(stderr, "%s: unterminated argument: %s\n", __func__, res);
return 1;
}
else if ((error = parse_arg(c, next, n)))
{
fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res);
return error;
}
return 0;
}
static int parse_adargs(struct ctx *const c, const char *const start,
const char *const res)
{
for (const char *arg = start, *next; arg; arg = next)
{
next = strchr(++arg, '&');
int error;
const size_t n = next ? next - arg : strlen(arg);
if ((error = parse_arg(c, arg, n)))
{
fprintf(stderr, "%s: parse_arg failed: %s\n", __func__, res);
return error;
}
}
return 0;
}
static int parse_args(struct ctx *const c, const char *const res,
size_t *const reslen)
{
int error;
const char *const arg_start = strchr(res, '?'),
*const ad_arg = strchr(res, '&');
if (!arg_start)
{
if (!ad_arg)
{
*reslen = strlen(res);
return 0;
}
else
{
fprintf(stderr, "%s: expected argument indicator '?': %s\n",
__func__, res);
return 1;
}
}
else if (arg_start == res)
{
fprintf(stderr, "%s: expected resource: %s\n", __func__, res);
return 1;
}
else if (ad_arg && ad_arg <= arg_start)
{
fprintf(stderr, "%s: expected '?' before '&': %s\n", __func__, res);
return 1;
}
else if ((error = parse_first_arg(c, arg_start, ad_arg, res)))
{
fprintf(stderr, "%s: parse_first_arg failed\n", __func__);
return error;
}
else if ((error = parse_adargs(c, ad_arg, res)))
{
fprintf(stderr, "%s: parse_adargs failed\n", __func__);
return error;
}
*reslen = arg_start - res;
return 0;
}
static int parse_resource(struct ctx *const c, const char *const enc_res)
{
int ret = -1, error;
size_t reslen;
char *trimmed_encres = NULL, *resource = NULL;
if (!(resource = http_decode_url(enc_res)))
{
fprintf(stderr, "%s: http_decode_url failed\n", __func__);
goto end;
}
else if ((error = parse_args(c, resource, &reslen)))
{
fprintf(stderr, "%s: parse_args failed\n", __func__);
ret = error;
goto end;
}
else if (!(trimmed_encres = strndup(resource, reslen)))
{
fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno));
goto end;
}
c->resource = trimmed_encres;
ret = 0;
end:
free(resource);
return ret;
}
static int start_line(struct http_ctx *const h)
{
const char *const line = (const char *)h->line;
@ -190,7 +405,7 @@ static int start_line(struct http_ctx *const h)
return 1;
}
int ret = 1;
int ret = 1, error;
char *enc_res = NULL;
if (get_version(h, protocol))
@ -204,9 +419,10 @@ static int start_line(struct http_ctx *const h)
ret = -1;
goto end;
}
else if (!(c->resource = http_decode_url(enc_res)))
else if ((error = parse_resource(c, enc_res)))
{
fprintf(stderr, "%s: http_decode_url failed\n", __func__);
fprintf(stderr, "%s: parse_resource failed\n", __func__);
ret = error;
goto end;
}
@ -255,6 +471,10 @@ static void ctx_free(struct ctx *const c)
free(c->resource);
free(c->boundary);
for (size_t i = 0; i < c->n_args; i++)
arg_free(&c->args[i]);
free(c->args);
*c = (const struct ctx){0};
}
@ -673,10 +893,9 @@ static int set_content_type(struct http_ctx *const h, const char *const type)
return 0;
}
static int payload_get(struct http_ctx *const h, const char *const line)
static struct http_payload ctx_to_payload(const struct ctx *const c)
{
struct ctx *const c = &h->ctx;
const struct http_payload p =
return (const struct http_payload)
{
.cookie =
{
@ -685,9 +904,16 @@ static int payload_get(struct http_ctx *const h, const char *const line)
},
.op = c->op,
.resource = c->resource
.resource = c->resource,
.args = c->args,
.n_args = c->n_args
};
}
static int payload_get(struct http_ctx *const h, const char *const line)
{
struct ctx *const c = &h->ctx;
const struct http_payload p = ctx_to_payload(c);
const int ret = h->cfg.payload(&p, &h->wctx.r, h->cfg.user);
ctx_free(c);
@ -701,18 +927,7 @@ static int payload_get(struct http_ctx *const h, const char *const line)
static int payload_post(struct http_ctx *const h, const char *const line)
{
struct ctx *const c = &h->ctx;
const struct http_payload pl =
{
.cookie =
{
.field = c->field,
.value = c->value
},
.op = c->op,
.resource = c->resource
};
const struct http_payload pl = ctx_to_payload(c);
const int ret = h->cfg.payload(&pl, &h->wctx.r, h->cfg.user);
ctx_free(c);

7
http.h
View File

@ -35,6 +35,13 @@ struct http_payload
} *files;
} post;
} u;
const struct http_arg
{
char *key, *value;
} *args;
size_t n_args;
};
#define HTTP_STATUSES \