Send response on quota exceeded

So far, slcl would just close the connection with a client when the
Content-Length of an incoming request exceeded the user quota, without
any meaningful information given back to the user.

Now, slcl responds with a HTML file with meaningful information about
the error.

Limitations:

- While this commits has been successfully tested on ungoogled-chromium,
LibreWolf (and I assume Firefox and any other derivates too) does not
seem to receive the response from the server.
    - However, this issue only occurred during local testing, but not
on remote instances.
This commit is contained in:
Xavier Del Campo Romero 2023-03-20 03:32:00 +01:00
parent d51b191ab7
commit d9bb874591
Signed by: xavi
GPG Key ID: 84FF3612A9BF43F2
7 changed files with 136 additions and 13 deletions

View File

@ -112,13 +112,14 @@ static int on_payload(const struct http_payload *const p,
}
static int on_length(const unsigned long long len,
const struct http_cookie *const c, void *const user)
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
struct client *const cl = user;
struct handler *const h = cl->h;
if (h->cfg.length)
return h->cfg.length(len, c, h->cfg.user);
return h->cfg.length(len, c, r, h->cfg.user);
return 0;
}

View File

@ -11,7 +11,7 @@ struct handler_cfg
{
const char *tmpdir;
int (*length)(unsigned long long len, const struct http_cookie *c,
void *user);
struct http_response *r, void *user);
void *user;
};

33
http.c
View File

@ -85,7 +85,7 @@ struct http_ctx
struct write_ctx
{
bool pending;
bool pending, close;
enum state state;
struct http_response r;
off_t n;
@ -372,6 +372,8 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
return rw_error(res, close);
else if ((w->n += res) >= d->len)
{
const bool close_pending = w->close;
dynstr_free(d);
if (w->r.n)
@ -384,6 +386,8 @@ static int write_header_cr_line(struct http_ctx *const h, bool *const close)
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
return -1;
}
else if (close_pending)
*close = true;
}
return 0;
@ -394,17 +398,24 @@ static int write_body_mem(struct http_ctx *const h, bool *const close)
struct write_ctx *const w = &h->wctx;
const struct http_response *const r = &w->r;
const size_t rem = r->n - w->n;
const int res = h->cfg.write((const char *)r->buf.ro + w->n, rem,
int res = h->cfg.write((const char *)r->buf.ro + w->n, rem,
h->cfg.user);
if (res <= 0)
return rw_error(res, close);
else if ((w->n += res) >= r->n)
{
const bool close_pending = w->close;
if (h->version == HTTP_1_0)
*close = true;
return write_ctx_free(w);
if ((res = write_ctx_free(w)))
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
else if (close_pending)
*close = true;
return res;
}
return 0;
@ -425,16 +436,21 @@ static int write_body_file(struct http_ctx *const h, bool *const close)
return -1;
}
const int res = h->cfg.write(buf, rem, h->cfg.user);
int res = h->cfg.write(buf, rem, h->cfg.user);
if (res <= 0)
return rw_error(res, close);
else if ((w->n += res) >= r->n)
{
const bool close_pending = w->close;
if (h->version == HTTP_1_0)
*close = true;
return write_ctx_free(w);
if ((res = write_ctx_free(w)))
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
else if (close_pending)
*close = true;
}
return 0;
@ -810,7 +826,7 @@ static int check_length(struct http_ctx *const h)
.value = c->value
};
return h->cfg.length(c->post.len, &cookie, h->cfg.user);
return h->cfg.length(c->post.len, &cookie, &h->wctx.r, h->cfg.user);
}
static int header_cr_line(struct http_ctx *const h)
@ -834,7 +850,10 @@ static int header_cr_line(struct http_ctx *const h)
const int res = check_length(h);
if (res)
return res;
{
h->wctx.close = true;
return start_response(h);
}
}
c->state = BODY_LINE;

3
http.h
View File

@ -45,6 +45,7 @@ struct http_payload
X(UNAUTHORIZED, "Unauthorized", 401) \
X(FORBIDDEN, "Forbidden", 403) \
X(NOT_FOUND, "Not found", 404) \
X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \
X(INTERNAL_ERROR, "Internal Server Error", 500)
struct http_response
@ -80,7 +81,7 @@ struct http_cfg
int (*payload)(const struct http_payload *p, struct http_response *r,
void *user);
int (*length)(unsigned long long len, const struct http_cookie *c,
void *user);
struct http_response *r, void *user);
const char *tmpdir;
void *user;
};

14
main.c
View File

@ -610,7 +610,8 @@ static int check_quota(const struct auth *const a, const char *const username,
}
static int check_length(const unsigned long long len,
const struct http_cookie *const c, void *const user)
const struct http_cookie *const c, struct http_response *const r,
void *const user)
{
struct auth *const a = user;
const char *const username = c->field;
@ -623,7 +624,16 @@ static int check_length(const unsigned long long len,
return -1;
}
else if (has_quota)
return check_quota(a, username, len, quota);
{
int res = check_quota(a, username, len, quota);
if (res < 0)
fprintf(stderr, "%s: check_quota failed\n", __func__);
else if (res > 0 && page_quota_exceeded(r, len, quota) < 0)
return -1;
return res;
}
return 0;
}

90
page.c
View File

@ -1311,3 +1311,93 @@ end:
return ret;
}
int page_quota_exceeded(struct http_response *const r,
const unsigned long long len, const unsigned long long quota)
{
int ret = -1;
struct dynstr msg, out;
char q[sizeof MAXSIZEFMT], l[sizeof q];
struct html_node *const html = html_node_alloc("html"),
*head, *body;
dynstr_init(&msg);
dynstr_init(&out);
if (!html)
{
fprintf(stderr, "%s: html_node_alloc failed\n", __func__);
goto end;
}
else if (!(head = html_node_add_child(html, "head")))
{
fprintf(stderr, "%s: html_node_add_child head failed\n", __func__);
goto end;
}
else if (!(body = html_node_add_child(html, "body")))
{
fprintf(stderr, "%s: html_node_add_child body failed\n", __func__);
goto end;
}
else if (common_head(head, NULL))
{
fprintf(stderr, "%s: common_head failed\n", __func__);
goto end;
}
else if (size_units(quota, q, sizeof q))
{
fprintf(stderr, "%s: size_units quota failed\n", __func__);
goto end;
}
else if (size_units(len, l, sizeof l))
{
fprintf(stderr, "%s: size_units len failed\n", __func__);
goto end;
}
else if (dynstr_append(&msg, "Maximum quota exceeded: %s "
"(requested size: %s)", q, l))
{
fprintf(stderr, "%s: dynstr_append msg failed\n", __func__);
goto end;
}
else if (html_node_set_value(body, msg.str))
{
fprintf(stderr, "%s: html_node_set_value msg failed\n", __func__);
goto end;
}
else if (dynstr_append(&out, DOCTYPE_TAG))
{
fprintf(stderr, "%s: dynstr_prepend failed\n", __func__);
goto end;
}
else if (html_serialize(html, &out))
{
fprintf(stderr, "%s: html_serialize failed\n", __func__);
goto end;
}
*r = (const struct http_response)
{
.status = HTTP_STATUS_PAYLOAD_TOO_LARGE,
.buf.rw = out.str,
.n = out.len,
.free = free
};
if (http_response_add_header(r, "Content-Type", "text/html"))
{
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
goto end;
}
ret = 0;
end:
html_node_free(html);
dynstr_free(&msg);
if (ret)
dynstr_free(&out);
return ret;
}

2
page.h
View File

@ -17,5 +17,7 @@ int page_resource(struct http_response *r, const char *dir, const char *root,
const char *res, const struct page_quota *q);
int page_public(struct http_response *r, const char *res);
int page_share(struct http_response *r, const char *path);
int page_quota_exceeded(struct http_response *r, unsigned long long len,
unsigned long long quota);
#endif /* PAGE_H */