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:
parent
d51b191ab7
commit
d9bb874591
|
@ -112,13 +112,14 @@ static int on_payload(const struct http_payload *const p,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int on_length(const unsigned long long len,
|
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 client *const cl = user;
|
||||||
struct handler *const h = cl->h;
|
struct handler *const h = cl->h;
|
||||||
|
|
||||||
if (h->cfg.length)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ struct handler_cfg
|
||||||
{
|
{
|
||||||
const char *tmpdir;
|
const char *tmpdir;
|
||||||
int (*length)(unsigned long long len, const struct http_cookie *c,
|
int (*length)(unsigned long long len, const struct http_cookie *c,
|
||||||
void *user);
|
struct http_response *r, void *user);
|
||||||
void *user;
|
void *user;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
33
http.c
33
http.c
|
@ -85,7 +85,7 @@ struct http_ctx
|
||||||
|
|
||||||
struct write_ctx
|
struct write_ctx
|
||||||
{
|
{
|
||||||
bool pending;
|
bool pending, close;
|
||||||
enum state state;
|
enum state state;
|
||||||
struct http_response r;
|
struct http_response r;
|
||||||
off_t n;
|
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);
|
return rw_error(res, close);
|
||||||
else if ((w->n += res) >= d->len)
|
else if ((w->n += res) >= d->len)
|
||||||
{
|
{
|
||||||
|
const bool close_pending = w->close;
|
||||||
|
|
||||||
dynstr_free(d);
|
dynstr_free(d);
|
||||||
|
|
||||||
if (w->r.n)
|
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__);
|
fprintf(stderr, "%s: write_ctx_free failed\n", __func__);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
else if (close_pending)
|
||||||
|
*close = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
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;
|
struct write_ctx *const w = &h->wctx;
|
||||||
const struct http_response *const r = &w->r;
|
const struct http_response *const r = &w->r;
|
||||||
const size_t rem = r->n - w->n;
|
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);
|
h->cfg.user);
|
||||||
|
|
||||||
if (res <= 0)
|
if (res <= 0)
|
||||||
return rw_error(res, close);
|
return rw_error(res, close);
|
||||||
else if ((w->n += res) >= r->n)
|
else if ((w->n += res) >= r->n)
|
||||||
{
|
{
|
||||||
|
const bool close_pending = w->close;
|
||||||
|
|
||||||
if (h->version == HTTP_1_0)
|
if (h->version == HTTP_1_0)
|
||||||
*close = true;
|
*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;
|
return 0;
|
||||||
|
@ -425,16 +436,21 @@ static int write_body_file(struct http_ctx *const h, bool *const close)
|
||||||
return -1;
|
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)
|
if (res <= 0)
|
||||||
return rw_error(res, close);
|
return rw_error(res, close);
|
||||||
else if ((w->n += res) >= r->n)
|
else if ((w->n += res) >= r->n)
|
||||||
{
|
{
|
||||||
|
const bool close_pending = w->close;
|
||||||
|
|
||||||
if (h->version == HTTP_1_0)
|
if (h->version == HTTP_1_0)
|
||||||
*close = true;
|
*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;
|
return 0;
|
||||||
|
@ -810,7 +826,7 @@ static int check_length(struct http_ctx *const h)
|
||||||
.value = c->value
|
.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)
|
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);
|
const int res = check_length(h);
|
||||||
|
|
||||||
if (res)
|
if (res)
|
||||||
return res;
|
{
|
||||||
|
h->wctx.close = true;
|
||||||
|
return start_response(h);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c->state = BODY_LINE;
|
c->state = BODY_LINE;
|
||||||
|
|
3
http.h
3
http.h
|
@ -45,6 +45,7 @@ struct http_payload
|
||||||
X(UNAUTHORIZED, "Unauthorized", 401) \
|
X(UNAUTHORIZED, "Unauthorized", 401) \
|
||||||
X(FORBIDDEN, "Forbidden", 403) \
|
X(FORBIDDEN, "Forbidden", 403) \
|
||||||
X(NOT_FOUND, "Not found", 404) \
|
X(NOT_FOUND, "Not found", 404) \
|
||||||
|
X(PAYLOAD_TOO_LARGE, "Payload too large", 413) \
|
||||||
X(INTERNAL_ERROR, "Internal Server Error", 500)
|
X(INTERNAL_ERROR, "Internal Server Error", 500)
|
||||||
|
|
||||||
struct http_response
|
struct http_response
|
||||||
|
@ -80,7 +81,7 @@ struct http_cfg
|
||||||
int (*payload)(const struct http_payload *p, struct http_response *r,
|
int (*payload)(const struct http_payload *p, struct http_response *r,
|
||||||
void *user);
|
void *user);
|
||||||
int (*length)(unsigned long long len, const struct http_cookie *c,
|
int (*length)(unsigned long long len, const struct http_cookie *c,
|
||||||
void *user);
|
struct http_response *r, void *user);
|
||||||
const char *tmpdir;
|
const char *tmpdir;
|
||||||
void *user;
|
void *user;
|
||||||
};
|
};
|
||||||
|
|
14
main.c
14
main.c
|
@ -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,
|
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;
|
struct auth *const a = user;
|
||||||
const char *const username = c->field;
|
const char *const username = c->field;
|
||||||
|
@ -623,7 +624,16 @@ static int check_length(const unsigned long long len,
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
else if (has_quota)
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
90
page.c
90
page.c
|
@ -1311,3 +1311,93 @@ end:
|
||||||
|
|
||||||
return ret;
|
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
2
page.h
|
@ -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);
|
const char *res, const struct page_quota *q);
|
||||||
int page_public(struct http_response *r, const char *res);
|
int page_public(struct http_response *r, const char *res);
|
||||||
int page_share(struct http_response *r, const char *path);
|
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 */
|
#endif /* PAGE_H */
|
||||||
|
|
Loading…
Reference in New Issue