2023-03-24 02:47:11 +01:00
|
|
|
#define _POSIX_C_SOURCE 200809L
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
#include "auth.h"
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
#include "cftw.h"
|
2023-03-09 01:29:48 +01:00
|
|
|
#include "hex.h"
|
2023-01-09 01:22:54 +01:00
|
|
|
#include "page.h"
|
2023-07-11 01:20:39 +02:00
|
|
|
#include "style.h"
|
2023-10-10 23:43:47 +02:00
|
|
|
#include <libweb/handler.h>
|
|
|
|
#include <libweb/http.h>
|
|
|
|
#include <libweb/wildcard_cmp.h>
|
2023-03-09 01:29:48 +01:00
|
|
|
#include <openssl/err.h>
|
|
|
|
#include <openssl/rand.h>
|
2023-01-09 01:22:54 +01:00
|
|
|
#include <dynstr.h>
|
2023-07-08 00:54:59 +02:00
|
|
|
#include <dirent.h>
|
2023-01-09 01:22:54 +01:00
|
|
|
#include <libgen.h>
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <limits.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
2023-07-11 01:20:39 +02:00
|
|
|
#define STYLE_PATH "style.css"
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
struct form
|
|
|
|
{
|
|
|
|
char *key, *value;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int redirect(struct http_response *const r)
|
|
|
|
{
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_SEE_OTHER
|
|
|
|
};
|
|
|
|
|
|
|
|
if (http_response_add_header(r, "Location", "/user/"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int serve_index(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
return page_login(r);
|
|
|
|
|
|
|
|
return redirect(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int serve_style(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
2023-07-11 01:20:39 +02:00
|
|
|
int ret = -1;
|
|
|
|
struct auth *const a = user;
|
|
|
|
const char *const dir = auth_dir(a);
|
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (!dir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%s/" STYLE_PATH, dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = page_style(r, d.str)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: page_style failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static char *alloc_form_data(const char *const s, const char **const end)
|
|
|
|
{
|
|
|
|
const char *const next = strchr(s, '&');
|
|
|
|
const size_t len = next ? next - s : strlen(s);
|
|
|
|
char *const data = malloc(len + 1);
|
|
|
|
|
|
|
|
if (!data)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(data, s, len);
|
|
|
|
data[len] = '\0';
|
|
|
|
*end = s + len;
|
|
|
|
|
|
|
|
if (next)
|
|
|
|
*end += 1;
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2023-03-08 00:38:03 +01:00
|
|
|
static void form_free(struct form *const f)
|
|
|
|
{
|
|
|
|
if (f)
|
|
|
|
{
|
|
|
|
free(f->key);
|
|
|
|
free(f->value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
static void forms_free(struct form *const f, const size_t n)
|
|
|
|
{
|
|
|
|
if (f)
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
form_free(&f[i]);
|
|
|
|
|
|
|
|
free(f);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int append_form(struct form **const forms, const char **const s,
|
2023-01-09 01:22:54 +01:00
|
|
|
size_t *const n)
|
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
int ret = -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
const char *end;
|
2023-11-12 00:01:35 +01:00
|
|
|
char *const data = alloc_form_data(*s, &end), *enckey = NULL,
|
|
|
|
*encvalue = NULL, *key = NULL, *value = NULL;
|
2023-03-19 13:08:09 +01:00
|
|
|
struct form *f = NULL, *fs = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (!data)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: alloc_form_data failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const sep = strchr(data, '=');
|
|
|
|
|
|
|
|
if (!sep)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strchr(3) returned NULL\n", __func__);
|
2023-03-19 13:08:09 +01:00
|
|
|
ret = 1;
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!data || !*(sep + 1))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected key=value (%s)\n", __func__, data);
|
2023-03-19 13:08:09 +01:00
|
|
|
ret = 1;
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const size_t keylen = sep - data;
|
|
|
|
|
2023-11-12 00:01:35 +01:00
|
|
|
if (!(enckey = strndup(data, keylen)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 00:01:35 +01:00
|
|
|
fprintf(stderr, "%s: strndup(3) enckey: %s\n",
|
|
|
|
__func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
2023-11-12 00:01:35 +01:00
|
|
|
else if (!(encvalue = strdup(sep + 1)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 00:01:35 +01:00
|
|
|
fprintf(stderr, "%s: strdup(3) encvalue: %s\n",
|
|
|
|
__func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
2023-03-19 13:08:09 +01:00
|
|
|
else if (!(fs = realloc(*forms, (*n + 1) * sizeof **forms)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
*forms = fs;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-04-30 23:43:10 +02:00
|
|
|
/* HTML input forms use '+' for whitespace, rather than %20. */
|
2023-11-12 00:01:35 +01:00
|
|
|
if ((ret = http_decode_url(enckey, true, &key)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 00:01:35 +01:00
|
|
|
fprintf(stderr, "%s: http_decode_url enckey failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = http_decode_url(encvalue, true, &value)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-12 00:01:35 +01:00
|
|
|
fprintf(stderr, "%s: http_decode_url encvalue failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-11-12 00:01:35 +01:00
|
|
|
f = &(*forms)[(*n)++];
|
|
|
|
|
|
|
|
*f = (const struct form)
|
|
|
|
{
|
|
|
|
.key = key,
|
|
|
|
.value = value
|
|
|
|
};
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
*s = end;
|
2023-03-19 13:08:09 +01:00
|
|
|
ret = 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
end:
|
2023-11-12 00:01:35 +01:00
|
|
|
if (ret)
|
|
|
|
{
|
|
|
|
free(key);
|
|
|
|
free(value);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(enckey);
|
|
|
|
free(encvalue);
|
2023-01-09 01:22:54 +01:00
|
|
|
free(data);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
static int get_forms(const struct http_payload *const pl,
|
|
|
|
struct form **const forms, size_t *const outn)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
int ret = -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
const struct http_post *const p = &pl->u.post;
|
2023-03-19 13:08:09 +01:00
|
|
|
struct form *f = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-09-09 02:30:37 +02:00
|
|
|
if (!p->data)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-NULL buffer\n", __func__);
|
2023-09-09 00:15:39 +02:00
|
|
|
ret = 1;
|
2023-03-19 13:08:09 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-09-09 02:30:37 +02:00
|
|
|
const char *s = p->data;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
*outn = 0;
|
|
|
|
|
|
|
|
while (*s)
|
2023-03-19 13:08:09 +01:00
|
|
|
if ((ret = append_form(&f, &s, outn)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: append_form failed\n", __func__);
|
|
|
|
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
*forms = f;
|
|
|
|
ret = 0;
|
2023-03-07 12:38:59 +01:00
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
end:
|
|
|
|
if (ret)
|
|
|
|
forms_free(f, *outn);
|
|
|
|
|
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int check_credentials(struct auth *const a,
|
|
|
|
const struct form *const forms, const size_t n, char **const cookie)
|
|
|
|
{
|
|
|
|
const char *username = NULL, *pwd = NULL;
|
|
|
|
|
|
|
|
*cookie = NULL;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->key, "username"))
|
|
|
|
username = f->value;
|
|
|
|
else if (!strcmp(f->key, "password"))
|
|
|
|
pwd = f->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!username || !pwd)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing credentials\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int ret = auth_login(a, username, pwd, cookie);
|
|
|
|
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: auth_login failed\n", __func__);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int login(const struct http_payload *const pl,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
int ret = -1;
|
|
|
|
size_t n = 0;
|
|
|
|
struct form *forms = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
struct auth *const a = user;
|
|
|
|
char *cookie = NULL;
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
if ((ret = get_forms(pl, &forms, &n)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
2023-03-19 13:08:09 +01:00
|
|
|
else if ((ret = check_credentials(a, forms, n, &cookie)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
fprintf(stderr, "%s: check_credentials failed\n", __func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
2023-03-19 13:08:09 +01:00
|
|
|
else if ((ret = redirect(r)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: redirect failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-03-19 13:08:09 +01:00
|
|
|
else if (cookie
|
|
|
|
&& (ret = http_response_add_header(r, "Set-Cookie", cookie)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
ret = 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
end:
|
2023-03-19 13:08:09 +01:00
|
|
|
forms_free(forms, n);
|
2023-01-09 01:22:54 +01:00
|
|
|
free(cookie);
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret > 0 && (ret = page_failed_login(r)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-06 00:56:48 +01:00
|
|
|
fprintf(stderr, "%s: page_failed_login failed\n", __func__);
|
|
|
|
return -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
return ret;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static int logout(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
const struct http_cookie *const c = &p->cookie;
|
|
|
|
const int res = auth_cookie(a, c);
|
|
|
|
|
|
|
|
if (res < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (res)
|
|
|
|
return page_forbidden(r);
|
|
|
|
|
|
|
|
int ret = -1;
|
|
|
|
struct dynstr d;
|
|
|
|
static const char date[] = "Thu, 1 Jan 1970 00:00:00 GMT";
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_SEE_OTHER
|
|
|
|
};
|
|
|
|
|
|
|
|
if (http_response_add_header(r, "Location", "/"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (http_response_add_header(r, "Content-Type", "text/html"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
/* Force expired cookie so they are removed by the browser, too. */
|
|
|
|
else if (dynstr_append(&d, "%s=%s; Expires=%s", c->field, c->value, date))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (http_response_add_header(r, "Set-Cookie", d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-03-09 01:29:48 +01:00
|
|
|
static bool path_isrel(const char *const path)
|
|
|
|
{
|
2024-02-19 16:59:22 +01:00
|
|
|
if (!strcmp(path, "..")
|
|
|
|
|| !strcmp(path, ".")
|
2024-02-20 21:24:17 +01:00
|
|
|
|| !strncmp(path, "./", strlen("./"))
|
|
|
|
|| !strncmp(path, "../", strlen("../"))
|
|
|
|
|| strstr(path, "/./")
|
2024-02-19 16:59:22 +01:00
|
|
|
|| strstr(path, "/../"))
|
2023-03-09 01:29:48 +01:00
|
|
|
return true;
|
|
|
|
|
2024-02-20 21:24:17 +01:00
|
|
|
static const char *const suffixes[] = {"/.", "/.."};
|
2023-03-09 01:29:48 +01:00
|
|
|
|
2024-02-20 21:24:17 +01:00
|
|
|
for (size_t i = 0; i < sizeof suffixes / sizeof *suffixes; i++)
|
|
|
|
{
|
|
|
|
const char *const suffix = suffixes[i];
|
|
|
|
const size_t n = strlen(path), sn = strlen(suffix);
|
|
|
|
|
|
|
|
if (n >= sn && !strcmp(path + n - sn, suffix))
|
|
|
|
return true;
|
|
|
|
}
|
2023-03-09 01:29:48 +01:00
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-07-09 04:41:55 +02:00
|
|
|
static bool path_invalid(const char *const path)
|
|
|
|
{
|
|
|
|
return path_isrel(path) || strchr(path, '*');
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool filename_invalid(const char *const path)
|
|
|
|
{
|
|
|
|
return path_invalid(path) || strchr(path, '/');
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool dirname_invalid(const char *const path)
|
|
|
|
{
|
|
|
|
return *path != '/' || path[strlen(path) - 1] != '/' || path_invalid(path);
|
|
|
|
}
|
|
|
|
|
2023-03-09 01:29:48 +01:00
|
|
|
static int getpublic(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct auth *const a = user;
|
2024-02-20 08:18:11 +01:00
|
|
|
const char *const adir = auth_dir(a),
|
|
|
|
*const file = p->resource + strlen("/public/");
|
2023-03-09 01:29:48 +01:00
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (!adir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
2024-02-20 08:18:11 +01:00
|
|
|
else if (!*file || filename_invalid(file))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid filename %s\n",
|
|
|
|
__func__, p->resource);
|
|
|
|
ret = page_forbidden(r);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (path_invalid(p->resource))
|
2023-03-09 01:29:48 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: illegal relative path %s\n",
|
|
|
|
__func__, p->resource);
|
|
|
|
ret = page_forbidden(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%s%s", adir, p->resource))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (page_public(r, d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: page_public failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *create_symlink(const char *const username, const char *const dir,
|
|
|
|
const char *const path)
|
|
|
|
{
|
|
|
|
char *ret = NULL;
|
|
|
|
unsigned char buf[16];
|
|
|
|
char dbuf[1 + 2 * sizeof buf];
|
|
|
|
struct dynstr user, abs, rel;
|
|
|
|
|
|
|
|
dynstr_init(&user);
|
|
|
|
dynstr_init(&abs);
|
|
|
|
dynstr_init(&rel);
|
|
|
|
|
|
|
|
if (RAND_bytes(buf, sizeof buf) != 1)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: RAND_bytes failed with %lu\n",
|
|
|
|
__func__, ERR_get_error());
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (hex_encode(buf, dbuf, sizeof buf, sizeof dbuf))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: hex_encode failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&user, "%s/user/%s%s", dir, username, path))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&rel, "/public/%s", dbuf))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append rel failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&abs, "%s%s", dir, rel.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append abs failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (symlink(user.str, abs.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: symlink(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = rel.str;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&user);
|
|
|
|
dynstr_free(&abs);
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
dynstr_free(&rel);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-06-06 03:49:36 +02:00
|
|
|
static int check_search_input(const struct http_payload *const p,
|
|
|
|
int (**const f)(struct http_response *), const struct auth *const a,
|
|
|
|
char **const dir, struct dynstr *const res)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-06-06 03:49:36 +02:00
|
|
|
int ret = auth_cookie(a, &p->cookie);
|
|
|
|
struct form *forms = NULL;
|
|
|
|
size_t n = 0;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-06-06 03:49:36 +02:00
|
|
|
if (ret < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
2023-06-06 03:49:36 +02:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (ret)
|
|
|
|
{
|
|
|
|
*f = page_forbidden;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = get_forms(p, &forms, &n)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
else
|
|
|
|
*f = page_bad_request;
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *tdir = NULL, *tres = NULL;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->key, "dir"))
|
|
|
|
tdir = f->value;
|
|
|
|
else if (!strcmp(f->key, "name"))
|
|
|
|
tres = f->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!tdir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-null directory\n", __func__);
|
|
|
|
ret = 1;
|
|
|
|
*f = page_bad_request;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!tres)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-null resource\n", __func__);
|
|
|
|
ret = 1;
|
|
|
|
*f = page_bad_request;
|
|
|
|
goto end;
|
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (dirname_invalid(tdir))
|
2023-06-06 03:49:36 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid directory %s\n", __func__, tdir);
|
|
|
|
ret = 1;
|
|
|
|
*f = page_bad_request;
|
|
|
|
goto end;
|
|
|
|
}
|
2023-07-11 13:26:06 +02:00
|
|
|
else if (path_isrel(tres))
|
2023-06-06 03:49:36 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid resource %s\n", __func__, tres);
|
|
|
|
ret = 1;
|
|
|
|
*f = page_bad_request;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(*dir = strdup(tdir)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strdup(3) dir: %s\n", __func__, strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(res, "*%s*", tres))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
ret = -1;
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
forms_free(forms, n);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void search_result_free(struct page_search_result *const r)
|
|
|
|
{
|
|
|
|
if (r)
|
|
|
|
free(r->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void search_results_free(struct page_search *const s)
|
|
|
|
{
|
|
|
|
if (s)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < s->n; i++)
|
|
|
|
search_result_free(&s->results[i]);
|
|
|
|
|
|
|
|
free(s->results);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct search_args
|
|
|
|
{
|
|
|
|
const char *root, *res;
|
|
|
|
struct page_search *s;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int search_fn(const char *const fpath, const struct stat *const sb,
|
2023-07-11 13:08:06 +02:00
|
|
|
bool *const done, void *const user)
|
2023-06-06 03:49:36 +02:00
|
|
|
{
|
2023-07-11 13:11:50 +02:00
|
|
|
static const size_t limit = 200;
|
2023-06-06 03:49:36 +02:00
|
|
|
const struct search_args *const sa = user;
|
|
|
|
const char *rel = fpath + strlen(sa->root);
|
|
|
|
struct page_search *const res = sa->s;
|
|
|
|
struct page_search_result *results = NULL, *r = NULL;
|
|
|
|
|
|
|
|
rel += strspn(rel, "/");
|
|
|
|
|
|
|
|
if (wildcard_cmp(rel, sa->res, false))
|
|
|
|
return 0;
|
|
|
|
else if (!(results = realloc(res->results,
|
|
|
|
(res->n + 1) * sizeof *res->results)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto failure;
|
|
|
|
}
|
|
|
|
|
|
|
|
r = &results[res->n];
|
|
|
|
*r = (const struct page_search_result)
|
|
|
|
{
|
|
|
|
.name = strdup(rel)
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!r->name)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: strdup(3): %s", __func__, strerror(errno));
|
|
|
|
goto failure;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2023-06-06 03:49:36 +02:00
|
|
|
res->results = results;
|
2023-07-11 13:11:50 +02:00
|
|
|
|
|
|
|
if (++res->n >= limit)
|
|
|
|
{
|
|
|
|
sa->s->limit_exceeded = true;
|
|
|
|
*done = true;
|
|
|
|
}
|
|
|
|
|
2023-06-06 03:49:36 +02:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
failure:
|
|
|
|
search_result_free(r);
|
2023-07-07 22:01:13 +02:00
|
|
|
free(results);
|
2023-01-09 01:22:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
2023-06-06 03:49:36 +02:00
|
|
|
|
|
|
|
static int do_search(const char *const abs, const char *const root,
|
|
|
|
const char *const res, struct page_search *const s)
|
|
|
|
{
|
|
|
|
struct search_args sa =
|
|
|
|
{
|
|
|
|
.root = root,
|
|
|
|
.res = res,
|
|
|
|
.s = s
|
|
|
|
};
|
|
|
|
|
|
|
|
s->root = root;
|
|
|
|
|
|
|
|
if (cftw(abs, search_fn, &sa))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: cftw failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int search(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const struct auth *const a = user;
|
|
|
|
const char *const username = p->cookie.field, *const root = auth_dir(a);
|
|
|
|
struct page_search s = {0};
|
|
|
|
int (*f)(struct http_response *);
|
|
|
|
char *dir = NULL;
|
|
|
|
struct dynstr userd, d, res;
|
|
|
|
|
|
|
|
dynstr_init(&userd);
|
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_init(&res);
|
|
|
|
|
|
|
|
if (!root)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = check_search_input(p, &f, a, &dir, &res)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: check_search_input failed\n", __func__);
|
|
|
|
else if ((ret = f(r)))
|
|
|
|
fprintf(stderr, "%s: check_search_input callback failed\n",
|
|
|
|
__func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&userd, "%s/user/%s", root, username))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append userd failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%s/%s", userd.str, dir + strspn(dir, "/")))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = do_search(d.str, userd.str, res.str, &s)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: do_search failed\n", __func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = page_search(r, &s)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: page_search failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(dir);
|
|
|
|
dynstr_free(&userd);
|
|
|
|
dynstr_free(&d);
|
|
|
|
dynstr_free(&res);
|
|
|
|
search_results_free(&s);
|
|
|
|
return ret;
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-03-09 01:29:48 +01:00
|
|
|
static int share(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
return page_forbidden(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const adir = auth_dir(a);
|
|
|
|
|
|
|
|
if (!adir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ret = -1;
|
|
|
|
struct form *forms = NULL;
|
2023-03-19 13:08:09 +01:00
|
|
|
size_t n = 0;
|
2023-03-09 01:29:48 +01:00
|
|
|
char *sympath = NULL;
|
|
|
|
|
2023-03-19 13:08:09 +01:00
|
|
|
if ((ret = get_forms(p, &forms, &n)))
|
2023-03-09 01:29:48 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
else
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
|
2023-03-09 01:29:48 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (n != 1)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected 1 form, got %zu\n", __func__, n);
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const path = forms->value, *const username = p->cookie.field;
|
|
|
|
|
2023-07-09 04:41:55 +02:00
|
|
|
if (path_invalid(path))
|
2023-03-09 01:29:48 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(sympath = create_symlink(username, adir, path)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: create_symlink failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (page_share(r, sympath))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: page_share failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
2023-03-19 13:08:09 +01:00
|
|
|
forms_free(forms, n);
|
2023-03-09 01:29:48 +01:00
|
|
|
free(sympath);
|
|
|
|
return ret;
|
|
|
|
}
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
|
|
|
|
static int add_length(const char *const fpath, const struct stat *const sb,
|
2023-07-11 13:08:06 +02:00
|
|
|
bool *const done, void *const user)
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
{
|
2023-07-08 02:30:31 +02:00
|
|
|
if (!S_ISREG(sb->st_mode))
|
|
|
|
return 0;
|
|
|
|
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
unsigned long long *const l = user;
|
|
|
|
|
|
|
|
*l += sb->st_size;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int quota_current(const struct auth *const a,
|
|
|
|
const char *const username, unsigned long long *const cur)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
const char *const adir = auth_dir(a);
|
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (!adir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%s/user/%s", adir, username))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
*cur = 0;
|
|
|
|
|
|
|
|
if (cftw(d.str, add_length, cur))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: cftw: %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int check_quota(const struct auth *const a, const char *const username,
|
|
|
|
const unsigned long long len, const unsigned long long quota)
|
|
|
|
{
|
|
|
|
unsigned long long total;
|
|
|
|
|
|
|
|
if (quota_current(a, username, &total))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: quota_current failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return total + len > quota ? 1 : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int check_length(const unsigned long long len,
|
2023-03-20 03:32:00 +01:00
|
|
|
const struct http_cookie *const c, struct http_response *const r,
|
|
|
|
void *const user)
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
const char *const username = c->field;
|
|
|
|
bool has_quota;
|
|
|
|
unsigned long long quota;
|
|
|
|
|
2024-02-20 00:17:40 +01:00
|
|
|
if (auth_cookie(a, c))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
|
|
|
|
if (page_forbidden(r))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (auth_quota(a, username, &has_quota, "a))
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_quota failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (has_quota)
|
2023-03-20 03:32:00 +01:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int getnode(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
2023-03-08 18:48:58 +01:00
|
|
|
return page_forbidden(r);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *const username = p->cookie.field,
|
|
|
|
*const resource = p->resource + strlen("/user/");
|
|
|
|
|
2023-07-09 04:41:55 +02:00
|
|
|
if (path_invalid(resource))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
|
|
|
|
return page_forbidden(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ret = -1;
|
2023-03-08 18:50:15 +01:00
|
|
|
struct dynstr dir, root, d;
|
|
|
|
const char *const adir = auth_dir(a),
|
|
|
|
*const sep = p->resource[strlen(p->resource) - 1] != '/' ? "/" : "";
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-03-08 18:50:15 +01:00
|
|
|
dynstr_init(&dir);
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_init(&root);
|
|
|
|
|
|
|
|
if (!adir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-03-08 18:50:15 +01:00
|
|
|
else if (dynstr_append(&dir, "%s%s", p->resource, sep))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-08 18:50:15 +01:00
|
|
|
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&root, "%s/user/%s/", adir, username)
|
|
|
|
|| dynstr_append(&d, "%s%s", root.str, resource))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
bool available;
|
|
|
|
unsigned long long cur, max;
|
|
|
|
|
|
|
|
if (auth_quota(a, username, &available, &max))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: quota_available failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (available && quota_current(a, username, &cur))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: quota_current failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
2023-04-23 05:19:27 +02:00
|
|
|
const struct page_resource pr =
|
|
|
|
{
|
|
|
|
.r = r,
|
|
|
|
.args = p->args,
|
|
|
|
.n_args = p->n_args,
|
|
|
|
.dir = dir.str,
|
|
|
|
.root = root.str,
|
|
|
|
.res = d.str,
|
|
|
|
.q = available ?
|
|
|
|
&(const struct page_quota) {.cur = cur, .max = max }
|
|
|
|
: NULL
|
|
|
|
};
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
|
2023-04-23 05:19:27 +02:00
|
|
|
ret = page_resource(&pr);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
end:
|
2023-03-08 18:50:15 +01:00
|
|
|
dynstr_free(&dir);
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_free(&d);
|
|
|
|
dynstr_free(&root);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-10-03 00:09:37 +02:00
|
|
|
static int getnode_head(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
struct auth *const a = user;
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
return page_forbidden(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
const char *const username = p->cookie.field,
|
|
|
|
*const resource = p->resource + strlen("/user/");
|
|
|
|
|
|
|
|
if (path_invalid(resource))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: illegal relative path %s\n", __func__, resource);
|
|
|
|
return page_forbidden(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ret = -1;
|
|
|
|
struct dynstr dir, root, d;
|
|
|
|
const char *const adir = auth_dir(a),
|
|
|
|
*const sep = p->resource[strlen(p->resource) - 1] != '/' ? "/" : "";
|
|
|
|
|
|
|
|
dynstr_init(&dir);
|
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_init(&root);
|
|
|
|
|
|
|
|
if (!adir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&dir, "%s%s", p->resource, sep))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&root, "%s/user/%s/", adir, username)
|
|
|
|
|| dynstr_append(&d, "%s%s", root.str, resource))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = page_head_resource(r, d.str);
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&dir);
|
|
|
|
dynstr_free(&d);
|
|
|
|
dynstr_free(&root);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int move_file(const char *const old, const char *const new)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
2024-02-19 23:04:50 +01:00
|
|
|
const int fd_old = open(old, O_RDONLY),
|
|
|
|
fd_new = open(new, O_WRONLY | O_CREAT, 0600);
|
2023-01-09 01:22:54 +01:00
|
|
|
struct stat sb;
|
|
|
|
|
2024-02-19 23:04:50 +01:00
|
|
|
if (fd_old < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
fprintf(stderr, "%s: open(2) fd_old: %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
2024-02-19 23:04:50 +01:00
|
|
|
else if (fd_new < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: open(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
2024-02-19 23:04:50 +01:00
|
|
|
else if (fstat(fd_old, &sb))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
fprintf(stderr, "%s: fstat(2): %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (off_t i = 0; i < sb.st_size;)
|
|
|
|
{
|
2023-10-19 15:32:22 +02:00
|
|
|
char buf[BUFSIZ];
|
2023-01-09 01:22:54 +01:00
|
|
|
const off_t left = sb.st_size - i;
|
|
|
|
const size_t rem = left > sizeof buf ? sizeof buf : left;
|
2024-02-19 23:04:50 +01:00
|
|
|
const ssize_t r = read(fd_old, buf, rem);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2024-02-19 23:04:50 +01:00
|
|
|
if (r < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
2024-02-19 23:04:50 +01:00
|
|
|
|
|
|
|
size_t wrem = r;
|
|
|
|
const void *p = buf;
|
|
|
|
|
|
|
|
while (wrem)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
const ssize_t w = write(fd_new, p, wrem);
|
|
|
|
|
|
|
|
if (w < 0)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: write(2): %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = (const char *)p + w;
|
|
|
|
wrem -= w;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
i += rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
2024-02-19 23:04:50 +01:00
|
|
|
if (fd_old >= 0 && close(fd_old))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
fprintf(stderr, "%s: close(2) fd_old: %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
2024-02-19 23:04:50 +01:00
|
|
|
if (fd_new >= 0 && close(fd_new))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2024-02-19 23:04:50 +01:00
|
|
|
fprintf(stderr, "%s: close(2) fd_new: %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rename_or_move(const char *const old, const char *const new)
|
|
|
|
{
|
|
|
|
const int res = rename(old, new);
|
|
|
|
|
|
|
|
if (res && errno == EXDEV)
|
|
|
|
return move_file(old, new);
|
|
|
|
else if (res)
|
|
|
|
fprintf(stderr, "%s: rename(3): %s\n", __func__, strerror(errno));
|
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2023-11-23 00:01:41 +01:00
|
|
|
static int check_upload_dir(const char *const dir)
|
|
|
|
{
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
if (stat(dir, &sb))
|
|
|
|
{
|
|
|
|
switch (errno)
|
|
|
|
{
|
|
|
|
case ENOENT:
|
|
|
|
/* Fall through. */
|
|
|
|
case ENOTDIR:
|
|
|
|
fprintf(stderr, "%s: cannot upload to non-existing dir %s\n",
|
|
|
|
__func__, dir);
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
|
|
|
__func__, dir, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!S_ISDIR(sb.st_mode))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: %s not a dir\n", __func__, dir);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int upload_file(const struct http_post_file *const f,
|
|
|
|
const char *const user, const char *const root, const char *const dir)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
2023-11-23 00:01:41 +01:00
|
|
|
struct dynstr dird, d;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-11-23 00:01:41 +01:00
|
|
|
dynstr_init(&dird);
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (!root)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-11-23 00:01:41 +01:00
|
|
|
else if (dynstr_append(&dird, "%s/user/%s/%s", root, user, dir))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-23 00:01:41 +01:00
|
|
|
fprintf(stderr, "%s: dynstr_append dird failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = check_upload_dir(dird.str)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: check_upload_dir failed\n", __func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "%s%s", dird.str, f->filename))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (rename_or_move(f->tmpname, d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rename_or_move failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
2023-11-23 00:01:41 +01:00
|
|
|
dynstr_free(&dird);
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int redirect_to_dir(const char *const dir,
|
|
|
|
struct http_response *const r)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct dynstr d;
|
|
|
|
char *const encdir = http_encode_url(dir);
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_SEE_OTHER
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!encdir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "/user%s", encdir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (http_response_add_header(r, "Location", d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
free(encdir);
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-09-09 00:47:14 +02:00
|
|
|
static const char *get_upload_dir(const struct http_post *const po)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < po->npairs; i++)
|
|
|
|
{
|
|
|
|
const struct http_post_pair *p = &po->pairs[i];
|
|
|
|
|
|
|
|
if (!strcmp(p->name, "dir"))
|
|
|
|
return p->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static int upload_files(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, const struct auth *const a)
|
|
|
|
{
|
|
|
|
const struct http_post *const po = &p->u.post;
|
|
|
|
const char *const root = auth_dir(a), *const user = p->cookie.field,
|
2023-09-09 00:47:14 +02:00
|
|
|
*const dir = get_upload_dir(po);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (!po->files)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected file list\n", __func__);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (!root)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (!dir)
|
|
|
|
{
|
|
|
|
static const char body[] = "<html>No target directory set</html>";
|
|
|
|
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_BAD_REQUEST,
|
|
|
|
.buf.ro = body,
|
|
|
|
.n = strlen(body)
|
|
|
|
};
|
|
|
|
|
|
|
|
if (http_response_add_header(r, "Content-Type", "text/html"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (dirname_invalid(dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid directory %s\n", __func__, dir);
|
|
|
|
return page_bad_request(r);
|
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-09-09 00:47:14 +02:00
|
|
|
for (size_t i = 0; i < po->nfiles; i++)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-11-23 00:01:41 +01:00
|
|
|
const int ret = upload_file(&po->files[i], user, root, dir);
|
|
|
|
|
|
|
|
if (ret < 0)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: upload_file failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-11-23 00:01:41 +01:00
|
|
|
else if (ret)
|
|
|
|
return page_bad_request(r);
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return redirect_to_dir(dir, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int upload(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
const struct auth *const a = user;
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
return page_forbidden(r);
|
|
|
|
}
|
2023-11-23 00:04:28 +01:00
|
|
|
else if (p->expect_continue)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_CONTINUE
|
|
|
|
};
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return upload_files(p, r, a);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int createdir(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
2023-03-08 00:38:03 +01:00
|
|
|
int ret = -1;
|
2023-01-09 01:22:54 +01:00
|
|
|
struct auth *const a = user;
|
2023-03-08 00:38:03 +01:00
|
|
|
struct dynstr d, userd;
|
|
|
|
struct form *forms = NULL;
|
2023-03-19 13:08:09 +01:00
|
|
|
size_t n = 0;
|
2024-02-19 23:07:16 +01:00
|
|
|
char *encurl = NULL;
|
2023-03-08 00:38:03 +01:00
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_init(&userd);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_forbidden(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-03-19 13:08:09 +01:00
|
|
|
else if ((ret = get_forms(p, &forms, &n)))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-03-19 13:08:09 +01:00
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
else
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
|
2023-03-08 00:38:03 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
else if (n != 2)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected 2 forms, got %zu\n", __func__, n);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
2024-02-19 23:08:35 +01:00
|
|
|
const char *name = NULL, *dir = NULL;
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->key, "name"))
|
|
|
|
name = f->value;
|
|
|
|
else if (!strcmp(f->key, "dir"))
|
|
|
|
dir = f->value;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: unexpected key %s\n", __func__, f->key);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!name || !dir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: missing name or directory\n", __func__);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (filename_invalid(name))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid directory name %s\n", __func__, dir);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (dirname_invalid(dir))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid name %s\n", __func__, name);
|
2023-03-08 00:38:03 +01:00
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
const char *const root = auth_dir(a);
|
|
|
|
|
|
|
|
if (!root)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
2023-03-19 13:08:09 +01:00
|
|
|
goto end;
|
2023-01-09 01:22:54 +01:00
|
|
|
}
|
2023-03-08 00:38:03 +01:00
|
|
|
else if (dynstr_append(&d, "%s/user/%s/%s%s",
|
|
|
|
root, p->cookie.field, dir, name))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append d failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&userd, "/user%s%s/", dir, name))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append userd failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (mkdir(d.str, 0700))
|
|
|
|
{
|
|
|
|
if (errno != EEXIST)
|
2023-03-08 00:37:28 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: mkdir(2): %s\n", __func__, strerror(errno));
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
2023-03-08 00:37:28 +01:00
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
static const char body[] = "<html>Directory already exists</html>";
|
|
|
|
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_BAD_REQUEST,
|
|
|
|
.buf.ro = body,
|
2023-03-06 05:24:13 +01:00
|
|
|
.n = sizeof body - 1
|
2023-01-09 01:22:54 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
if (http_response_add_header(r, "Content-Type", "text/html"))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_SEE_OTHER
|
|
|
|
};
|
|
|
|
|
2024-02-19 23:07:16 +01:00
|
|
|
if (!(encurl = http_encode_url(userd.str)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_encode_url failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (http_response_add_header(r, "Location", encurl))
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
2023-03-19 13:08:09 +01:00
|
|
|
forms_free(forms, n);
|
2023-01-09 01:22:54 +01:00
|
|
|
dynstr_free(&userd);
|
|
|
|
dynstr_free(&d);
|
2024-02-19 23:07:16 +01:00
|
|
|
free(encurl);
|
2023-01-09 01:22:54 +01:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-07-08 00:54:59 +02:00
|
|
|
static int check_rm_input(const struct form *const forms, const size_t n,
|
|
|
|
struct page_rm *const rm, int (**const cb)(struct http_response *))
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->key, "dir"))
|
|
|
|
{
|
|
|
|
if (!rm->dir)
|
|
|
|
rm->dir = f->value;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: directory defined more than once\n",
|
|
|
|
__func__);
|
|
|
|
*cb = page_bad_request;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!strcmp(f->key, "path"))
|
|
|
|
{
|
2023-07-09 04:41:55 +02:00
|
|
|
const char *const path = f->value;
|
|
|
|
|
|
|
|
if (path_isrel(path))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid path %s\n", __func__, rm->dir);
|
|
|
|
*cb = page_bad_request;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2023-07-08 00:54:59 +02:00
|
|
|
const char **tmp = realloc(rm->items, (rm->n + 1) * sizeof *tmp);
|
|
|
|
|
|
|
|
if (!tmp)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: realloc(3): %s\n",
|
|
|
|
__func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-07-09 04:41:55 +02:00
|
|
|
tmp[rm->n++] = path;
|
2023-07-08 00:54:59 +02:00
|
|
|
rm->items = tmp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rm->dir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-null dir\n", __func__);
|
|
|
|
*cb = page_bad_request;
|
|
|
|
return 1;
|
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (dirname_invalid(rm->dir))
|
2023-07-08 00:54:59 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid directory %s\n", __func__, rm->dir);
|
|
|
|
*cb = page_bad_request;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int confirm_rm(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct auth *const a = user;
|
|
|
|
struct form *forms = NULL;
|
|
|
|
const char **items = NULL;
|
|
|
|
size_t n = 0;
|
|
|
|
int (*f)(struct http_response *);
|
|
|
|
struct page_rm rm = {0};
|
|
|
|
|
|
|
|
if (auth_cookie(a, &p->cookie))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
ret = page_forbidden(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = get_forms(p, &forms, &n)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
else
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = check_rm_input(forms, n, &rm, &f)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: check_rm_input failed\n", __func__);
|
|
|
|
else if ((ret = f(r)))
|
|
|
|
fprintf(stderr, "%s: check_rm_input callback failed\n",
|
|
|
|
__func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = page_rm(r, &rm)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: page_rm failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
forms_free(forms, n);
|
|
|
|
free(items);
|
|
|
|
free(rm.items);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char *find_rm_dir(const struct form *const forms, const size_t n,
|
|
|
|
int (**const cb)(struct http_response *))
|
|
|
|
{
|
|
|
|
const char *dir = NULL;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
|
|
|
if (!strcmp(f->key, "dir"))
|
|
|
|
{
|
|
|
|
if (!dir)
|
|
|
|
dir = f->value;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: directory defined more than once\n",
|
|
|
|
__func__);
|
|
|
|
*cb = page_bad_request;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return dir;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rm_dir_contents(const char *const fpath,
|
2023-07-11 13:08:06 +02:00
|
|
|
const struct stat *const sb, bool *const done, void *const user)
|
2023-07-08 00:54:59 +02:00
|
|
|
{
|
|
|
|
if (S_ISDIR(sb->st_mode) && rmdir(fpath))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rmdir(2) %s: %s\n",
|
|
|
|
__func__, fpath, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
else if (S_ISREG(sb->st_mode) && remove(fpath))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: remove(3) %s: %s\n",
|
|
|
|
__func__, fpath, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rmdir_r(const char *const path)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
DIR *const d = opendir(path);
|
|
|
|
|
|
|
|
if (!d)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: opendir(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (cftw(path, rm_dir_contents, NULL))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rm_dir_contents failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (rmdir(path))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rmdir(2) %s: %s\n",
|
|
|
|
__func__, path, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (d && closedir(d))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: closedir(3): %s\n", __func__, strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rm_item(const char *const root, const char *const item)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct stat sb;
|
|
|
|
struct dynstr d;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (dynstr_append(&d, "%s/%s", root, item))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (stat(d.str, &sb))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: stat(2) %s: %s\n",
|
|
|
|
__func__, d.str, strerror(errno));
|
|
|
|
|
|
|
|
/* This might have already been removed from another request,
|
|
|
|
* and thus this situation should not be considered an error. */
|
|
|
|
if (errno == ENOENT || errno == ENOTDIR)
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (S_ISDIR(sb.st_mode) && rmdir_r(d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rmdir_r failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (S_ISREG(sb.st_mode) && remove(d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: remove(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int do_rm(const struct form *const forms, const size_t n,
|
|
|
|
const char *const dir)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < n; i++)
|
|
|
|
{
|
|
|
|
const struct form *const f = &forms[i];
|
|
|
|
|
2023-07-09 04:41:55 +02:00
|
|
|
if (!strcmp(f->key, "path"))
|
2023-07-08 00:54:59 +02:00
|
|
|
{
|
2023-07-09 04:41:55 +02:00
|
|
|
const char *const path = f->value;
|
|
|
|
|
|
|
|
if (path_isrel(path))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid path %s\n", __func__, path);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else if (rm_item(dir, f->value))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: rm_item failed\n", __func__);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-07-08 00:54:59 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int rm(const struct http_payload *const p,
|
|
|
|
struct http_response *const r, void *const user)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct auth *const a = user;
|
|
|
|
struct form *forms = NULL;
|
|
|
|
size_t n = 0;
|
|
|
|
const struct http_cookie *const c = &p->cookie;
|
|
|
|
const char *username = c->field, *adir;
|
|
|
|
struct dynstr d, userdir;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
dynstr_init(&userdir);
|
|
|
|
|
|
|
|
if (auth_cookie(a, c))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_cookie failed\n", __func__);
|
|
|
|
ret = page_forbidden(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!(adir = auth_dir(a)))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: auth_dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = get_forms(p, &forms, &n)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: get_forms failed\n", __func__);
|
|
|
|
else
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
int (*f)(struct http_response *);
|
|
|
|
const char *const dir = find_rm_dir(forms, n, &f);
|
|
|
|
|
|
|
|
if (!dir)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: expected non-null directory\n", __func__);
|
|
|
|
ret = f(r);
|
|
|
|
goto end;
|
|
|
|
}
|
2023-07-09 04:41:55 +02:00
|
|
|
else if (dirname_invalid(dir))
|
2023-07-08 00:54:59 +02:00
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: invalid directory %s\n", __func__, dir);
|
|
|
|
ret = page_bad_request(r);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&userdir, "%s/user/%s%s", adir, username, dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if ((ret = do_rm(forms, n, userdir.str)))
|
|
|
|
{
|
|
|
|
if (ret < 0)
|
|
|
|
fprintf(stderr, "%s: do_rm failed\n", __func__);
|
|
|
|
else if ((ret = f(r)))
|
|
|
|
fprintf(stderr, "%s: rm callback failed\n", __func__);
|
|
|
|
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&d, "/user%s", dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
*r = (const struct http_response)
|
|
|
|
{
|
|
|
|
.status = HTTP_STATUS_SEE_OTHER
|
|
|
|
};
|
|
|
|
|
|
|
|
if (http_response_add_header(r, "Location", d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: http_response_add_header failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
dynstr_free(&userdir);
|
|
|
|
forms_free(forms, n);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-01-09 01:22:54 +01:00
|
|
|
static void usage(char *const argv[])
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s [-t tmpdir] [-p port] dir\n", *argv);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_args(const int argc, char *const argv[],
|
|
|
|
const char **const dir, unsigned short *const port,
|
|
|
|
const char **const tmpdir)
|
|
|
|
{
|
|
|
|
const char *const envtmp = getenv("TMPDIR");
|
|
|
|
int opt;
|
|
|
|
|
|
|
|
/* Default values. */
|
|
|
|
*port = 0;
|
|
|
|
*tmpdir = envtmp ? envtmp : "/tmp";
|
|
|
|
|
|
|
|
while ((opt = getopt(argc, argv, "t:p:")) != -1)
|
|
|
|
{
|
|
|
|
switch (opt)
|
|
|
|
{
|
|
|
|
case 't':
|
|
|
|
*tmpdir = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'p':
|
|
|
|
{
|
2023-04-30 22:12:57 +02:00
|
|
|
char *endptr;
|
|
|
|
const unsigned long portul = strtoul(optarg, &endptr, 10);
|
2023-01-09 01:22:54 +01:00
|
|
|
|
2023-04-30 22:12:57 +02:00
|
|
|
if (*endptr || portul > UINT16_MAX)
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
2023-04-30 22:12:57 +02:00
|
|
|
fprintf(stderr, "%s: invalid port %s\n", __func__, optarg);
|
2023-01-09 01:22:54 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*port = portul;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
usage(argv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind >= argc)
|
|
|
|
{
|
|
|
|
usage(argv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
*dir = argv[optind];
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-03-16 01:46:49 +01:00
|
|
|
static int ensure_dir(const char *const dir)
|
|
|
|
{
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
if (stat(dir, &sb))
|
|
|
|
{
|
|
|
|
switch (errno)
|
|
|
|
{
|
|
|
|
case ENOENT:
|
|
|
|
if (mkdir(dir, S_IRWXU))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: mkdir(2) %s: %s\n",
|
|
|
|
__func__, dir, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("Created empty directory at %s\n", dir);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
2023-03-25 09:49:24 +01:00
|
|
|
else if (!S_ISDIR(sb.st_mode))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: %s not a directory\n", __func__, dir);
|
|
|
|
return -1;
|
|
|
|
}
|
2023-03-16 01:46:49 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int init_dirs(const char *const dir)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct dynstr user, public;
|
|
|
|
struct sb;
|
|
|
|
|
|
|
|
dynstr_init(&user);
|
|
|
|
dynstr_init(&public);
|
|
|
|
|
|
|
|
if (dynstr_append(&user, "%s/user", dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (dynstr_append(&public, "%s/public", dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append public failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (ensure_dir(dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: ensure_dir dir failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (ensure_dir(user.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: ensure_dir user failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (ensure_dir(public.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: ensure_dir public failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&user);
|
|
|
|
dynstr_free(&public);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-07-11 01:20:39 +02:00
|
|
|
static int dump_default_style(const char *const path)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
FILE *const f = fopen(path, "wb");
|
|
|
|
|
|
|
|
if (!f)
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fopen(3) %s: %s\n",
|
|
|
|
__func__, path, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (!fwrite(style_default, style_default_len, 1, f))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fwrite(3): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
printf("Dumped default stylesheet into %s\n", path);
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (f && fclose(f))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: fclose(3): %s\n", __func__, strerror(errno));
|
|
|
|
ret = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ensure_style(const char *const dir)
|
|
|
|
{
|
|
|
|
int ret = -1;
|
|
|
|
struct dynstr d;
|
|
|
|
struct stat sb;
|
|
|
|
|
|
|
|
dynstr_init(&d);
|
|
|
|
|
|
|
|
if (dynstr_append(&d, "%s/" STYLE_PATH, dir))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dynstr_append user failed\n", __func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
else if (stat(d.str, &sb))
|
|
|
|
{
|
|
|
|
switch (errno)
|
|
|
|
{
|
|
|
|
case ENOENT:
|
|
|
|
if (dump_default_style(d.str))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: dump_default_style failed\n",
|
|
|
|
__func__);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "%s: stat(2): %s\n", __func__, strerror(errno));
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!S_ISREG(sb.st_mode))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: %s not a regular file\n", __func__, d.str);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
|
|
|
|
end:
|
|
|
|
dynstr_free(&d);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2023-09-16 01:46:07 +02:00
|
|
|
static int add_urls(struct handler *const h, void *const user)
|
|
|
|
{
|
|
|
|
static const struct url
|
|
|
|
{
|
|
|
|
const char *url;
|
|
|
|
enum http_op op;
|
|
|
|
handler_fn f;
|
|
|
|
} urls[] =
|
|
|
|
{
|
|
|
|
{.url = "/", .op = HTTP_OP_GET, .f = serve_index},
|
|
|
|
{.url = "/index.html", .op = HTTP_OP_GET, .f = serve_index},
|
|
|
|
{.url = "/style.css", .op = HTTP_OP_GET, .f = serve_style},
|
|
|
|
{.url = "/user/*", .op = HTTP_OP_GET, .f = getnode},
|
2023-10-03 00:09:37 +02:00
|
|
|
{.url = "/user/*", .op = HTTP_OP_HEAD, .f = getnode_head},
|
2023-09-16 01:46:07 +02:00
|
|
|
{.url = "/login", .op = HTTP_OP_POST, .f = login},
|
|
|
|
{.url = "/logout", .op = HTTP_OP_POST, .f = logout},
|
|
|
|
{.url = "/public/*", .op = HTTP_OP_GET, .f = getpublic},
|
|
|
|
{.url = "/search", .op = HTTP_OP_POST, .f = search},
|
|
|
|
{.url = "/share", .op = HTTP_OP_POST, .f = share},
|
|
|
|
{.url = "/upload", .op = HTTP_OP_POST, .f = upload},
|
|
|
|
{.url = "/mkdir", .op = HTTP_OP_POST, .f = createdir},
|
|
|
|
{.url = "/confirm/rm", .op = HTTP_OP_POST, .f = confirm_rm},
|
|
|
|
{.url = "/rm", .op = HTTP_OP_POST, .f = rm}
|
|
|
|
};
|
|
|
|
|
|
|
|
for (size_t i = 0; i < sizeof urls / sizeof *urls; i++)
|
|
|
|
{
|
|
|
|
const struct url *const u = &urls[i];
|
|
|
|
|
|
|
|
if (handler_add(h, u->url, u->op, u->f, user))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: handler_add %s failed\n", __func__, u->url);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-07-06 00:56:19 +02:00
|
|
|
int main(int argc, char *argv[])
|
2023-01-09 01:22:54 +01:00
|
|
|
{
|
|
|
|
int ret = EXIT_FAILURE;
|
|
|
|
struct handler *h = NULL;
|
|
|
|
struct auth *a = NULL;
|
|
|
|
const char *dir, *tmpdir;
|
|
|
|
unsigned short port;
|
|
|
|
|
|
|
|
if (parse_args(argc, argv, &dir, &port, &tmpdir)
|
2023-03-16 01:46:49 +01:00
|
|
|
|| init_dirs(dir)
|
2023-07-11 01:20:39 +02:00
|
|
|
|| ensure_style(dir)
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
|| !(a = auth_alloc(dir)))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
const struct handler_cfg cfg =
|
|
|
|
{
|
|
|
|
.length = check_length,
|
|
|
|
.tmpdir = tmpdir,
|
2024-02-19 23:03:16 +01:00
|
|
|
.user = a,
|
|
|
|
.post =
|
|
|
|
{
|
|
|
|
/* Arbitrary limit. */
|
|
|
|
.max_files = 10000,
|
|
|
|
/* File upload only requires one pair. */
|
|
|
|
.max_pairs = 1
|
|
|
|
}
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
};
|
|
|
|
|
2023-11-23 00:04:28 +01:00
|
|
|
unsigned short outport;
|
|
|
|
|
Implement user quota
This feature allows admins to set a specific quota for each user, in
MiB. This feature is particularly useful for shared instances, where
unlimited user storage might be unfeasible or even dangerous for the
server.
Also, a nice HTML5 <progress> element has been added to the site that
shows how much of the quota has been consumed.
If no quota is set, slcl falls back to the default behaviour i.e.,
assume unlimited storage.
Limitations:
- While HTTP does specify a Content-Length, which determines the length
of the whole request, it does not specify how many files are involved
or their individual sizes.
- Because of this, if multiple files are uploaded simultaneously, the
whole request would be dropped if user quota is exceeded, even if not
all files exceeded it.
- Also, Content-Length adds the length of some HTTP boilerplate
(e.g.: boundaries), but slcl must rely on this before accepting the
whole request. In other words, this means some requests might be
rejected by slcl because of the extra bytes caused by such boilerplate.
- When the quota is exceeded, slcl must close the connection so that
the rest of the transfer is cancelled. Unfortunately, this means no
HTML can be sent back to the customer to inform about the situation.
2023-03-06 05:09:56 +01:00
|
|
|
if (!(h = handler_alloc(&cfg))
|
2023-09-16 01:46:07 +02:00
|
|
|
|| add_urls(h, a)
|
2023-11-23 00:04:28 +01:00
|
|
|
|| handler_listen(h, port, &outport))
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
printf("Listening on port %hu\n", outport);
|
|
|
|
|
|
|
|
if (handler_loop(h))
|
|
|
|
{
|
|
|
|
fprintf(stderr, "%s: handler_loop failed\n", __func__);
|
2023-01-09 01:22:54 +01:00
|
|
|
goto end;
|
2023-11-23 00:04:28 +01:00
|
|
|
}
|
2023-01-09 01:22:54 +01:00
|
|
|
|
|
|
|
ret = EXIT_SUCCESS;
|
|
|
|
|
|
|
|
end:
|
|
|
|
auth_free(a);
|
|
|
|
handler_free(h);
|
|
|
|
return ret;
|
|
|
|
}
|