initial public release

parent 86cdd8ce
((c-mode . ((indent-tabs-mode . t)
(c-file-style . "BSD"))))
...@@ -38,3 +38,10 @@ vmod_*.rst ...@@ -38,3 +38,10 @@ vmod_*.rst
*_options.rst *_options.rst
*_synopsis.rst *_synopsis.rst
vmod_*.3 vmod_*.3
# extracted headers
src/vmod_blob.h
src/vmod_blobdigest.h
# header extractor
src/vmod_get_h
========
vmod-tus
========
This version is for Varnish-Cache master as of 2020-08-17
(post-6.4.0).
DESCRIPTION
===========
.. _tus: https://tus.io/protocols/resumable-upload.html
.. _vmod_blobdigest: https://code.uplex.de/uplex-varnish/libvmod-blobdigest
This vmod implements a `tus`_ proxy which collects uploads on the
varnish server to send them to a backend in one go for storage. It
does not implement any permanent storage itself.
Besides the basic resumable uploads specified as the `tus`_ core
protocol, all currently defined extensions are supported, in
particular concatenation uploads.
See vmod documentation/man page for details on how to use it.
INSTALLATION
============
The source tree is based on autotools to configure the building, and
does also have the necessary bits in place to do functional unit tests
using the ``varnishtest`` tool.
Besides the Varnish header files (as located via ``pkg-config``), this
vmod also requires
* The ``VARNISHSRC`` Variable to point to the varnish-cache sources
used for building the installed varnish version.
* `vmod_blobdigest`_ to be installed before building this vmod if
hashes are to be used.
This vmod will need to be rebuild specifically for each version of
varnish and `vmod_blobdigest`_ as it requires internal interfaces.
Usage::
export VARNISHSRC=/path/to/your/varnish/sources
./autogen.sh
./configure
If you have installed Varnish to a non-standard directory, call
``autogen.sh`` and ``configure`` with ``PKG_CONFIG_PATH`` pointing to
the appropriate path. For instance, when varnishd configure was called
with ``--prefix=$PREFIX``, use
::
export PKG_CONFIG_PATH=${PREFIX}/lib/pkgconfig
export ACLOCAL_PATH=${PREFIX}/share/aclocal
The module will inherit its prefix from Varnish, unless you specify a
different ``--prefix`` when running the ``configure`` script for this
module.
Make targets:
* make - builds the vmod.
* make install - installs your vmod.
* make check - runs the unit tests in ``src/vtc/*.vtc``.
* make distcheck - run check and prepare a tarball of the vmod.
If you build a dist tarball, you don't need any of the autotools or
pkg-config. You can build the module simply by running::
./configure
make
Installation directories
------------------------
By default, the vmod ``configure`` script installs the built vmod in the
directory relevant to the prefix. The vmod installation directory can be
overridden by passing the ``vmoddir`` variable to ``make install``.
...@@ -3,6 +3,7 @@ AC_INIT([libvmod-tus], [0.1]) ...@@ -3,6 +3,7 @@ AC_INIT([libvmod-tus], [0.1])
AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_HEADER([config.h]) AC_CONFIG_HEADER([config.h])
AC_GNU_SOURCE
AM_INIT_AUTOMAKE([1.12 -Wall -Werror foreign parallel-tests]) AM_INIT_AUTOMAKE([1.12 -Wall -Werror foreign parallel-tests])
AM_SILENT_RULES([yes]) AM_SILENT_RULES([yes])
...@@ -20,6 +21,22 @@ AC_ARG_WITH([rst2man], ...@@ -20,6 +21,22 @@ AC_ARG_WITH([rst2man],
VARNISH_PREREQ([6.0.0]) VARNISH_PREREQ([6.0.0])
VARNISH_VMODS([tus]) VARNISH_VMODS([tus])
AC_ARG_VAR([VARNISHSRC], [path to Varnish source])
if test "x$VARNISHSRC" = x; then
AC_MSG_FAILURE([Need VARNISHSRC])
fi
VARNISHAPI_CFLAGS="$VARNISHAPI_CFLAGS -I$VARNISHSRC/bin/varnishd"
AC_PATH_TOOL(VMOD_BLOB, [libvmod_blob.so],
[AC_MSG_WARN([vmod_blob not found, checksum support will be disabled])],
[${VARNISHAPI_VMODDIR}:${vmoddir}])
AC_SUBST(VMOD_BLOB)
AC_PATH_TOOL(VMOD_BLOBDIGEST, [libvmod_blobdigest.so],
[AC_MSG_WARN([vmod_blob not found, checksum support will be disabled])],
[${VARNISHAPI_VMODDIR}:${vmoddir}])
AC_SUBST(VMOD_BLOBDIGEST)
AM_CONDITIONAL(CHKSUM, test x$VMOD_BLOB != x && test x$VMOD_BLOBDIGEST != x)
AC_CONFIG_FILES([ AC_CONFIG_FILES([
Makefile Makefile
...@@ -40,4 +57,7 @@ AS_ECHO(" ...@@ -40,4 +57,7 @@ AS_ECHO("
compiler: $CC compiler: $CC
cflags: $CFLAGS cflags: $CFLAGS
ldflags: $LDFLAGS ldflags: $LDFLAGS
blob: $VMOD_BLOB
blobdigest: $VMOD_BLOBDIGEST
") ")
AM_CFLAGS = $(VARNISHAPI_CFLAGS) AM_CFLAGS = $(VARNISHAPI_CFLAGS)
# Modules # ----------------------------------------
# get header from ext vmod
bin_PROGRAMS = vmod_get_h
vmod_get_h_SOURCES = \
vmod_get_h.c
vmod_get_h_LDADD = -ldl
# ----------------------------------------
# ext vmods
if CHKSUM
vmod_blob.h: vmod_get_h $(VMOD_BLOB)
$(builddir)/vmod_get_h blob $(VMOD_BLOB) >$@
vmod_blobdigest.h: vmod_get_h $(VMOD_BLOBDIGEST)
$(builddir)/vmod_get_h blobdigest $(VMOD_BLOBDIGEST) >$@
tus_blob.c: vmod_blob.h vmod_blobdigest.h
libvmod_tus_la_CFLAGS = $(AM_CFLAGS) -DHAVE_CHKSUM=1
endif
# ----------------------------------------
# vmod
vmod_LTLIBRARIES = \ vmod_LTLIBRARIES = \
libvmod_tus.la libvmod_tus.la
libvmod_tus_la_LDFLAGS = $(VMOD_LDFLAGS) libvmod_tus_la_LDFLAGS = $(VMOD_LDFLAGS)
libvmod_tus_la_SOURCES = vmod_tus.c libvmod_tus_la_SOURCES = \
tbl_hash_enum.h \
tbl_method.h \
tus_b64.h \
tus_blob.c \
tus_blob.h \
tus_concat.h \
tus_file.c \
tus_file.h \
tus_file_exp.c \
tus_file_exp.h \
tus_hdr.c \
tus_hdr.h \
tus_hex.h \
tus_request.c \
tus_request.h \
tus_response.c \
tus_response.h \
tus_server.h \
tus_servers.c \
tus_servers.h \
tus_stv.c \
tus_stv.h \
vmod_tus.c
nodist_libvmod_tus_la_SOURCES = \ nodist_libvmod_tus_la_SOURCES = \
vcc_tus_if.c \ vcc_tus_if.c \
vcc_tus_if.h vcc_tus_if.h
...@@ -19,13 +66,16 @@ AM_TESTS_ENVIRONMENT = \ ...@@ -19,13 +66,16 @@ AM_TESTS_ENVIRONMENT = \
PATH="$(abs_builddir):$(VARNISH_TEST_PATH):$(PATH)" \ PATH="$(abs_builddir):$(VARNISH_TEST_PATH):$(PATH)" \
LD_LIBRARY_PATH="$(VARNISH_LIBRARY_PATH)" LD_LIBRARY_PATH="$(VARNISH_LIBRARY_PATH)"
TEST_EXTENSIONS = .vtc TEST_EXTENSIONS = .vtc
VTC_LOG_COMPILER = varnishtest -v VTC_LOG_COMPILER = varnishtest -v -t 10
AM_VTC_LOG_FLAGS = \ AM_VTC_LOG_FLAGS = \
-p vcl_path="$(abs_top_srcdir)/vcl:$(VARNISHAPI_VCLDIR)" \ -p vcl_path="$(abs_top_srcdir)/vcl:$(VARNISHAPI_VCLDIR)" \
-p vmod_path="$(abs_builddir)/.libs:$(vmoddir):$(VARNISHAPI_VMODDIR)" -p vmod_path="$(abs_builddir)/.libs:$(vmoddir):$(VARNISHAPI_VMODDIR)"
TESTS = \ TESTS = \
vtc/vmod_tus.vtc vtc/plain.vtc \
vtc/name_hash.vtc \
vtc/expires.vtc \
vtc/done.vtc
# Documentation # Documentation
......
// HENUM(tusname, blobdigestenum)
HENUM(crc32, CRC32)
HENUM(icrc32, ICRC32)
HENUM(md5, MD5)
HENUM(rs, RS)
HENUM(sha1, SHA1)
HENUM(sha224, SHA224)
HENUM(sha256, SHA256)
HENUM(sha384, SHA384)
HENUM(sha3_224, SHA3_224)
HENUM(sha3_256, SHA3_256)
HENUM(sha3_384, SHA3_384)
HENUM(sha3_512, SHA3_512)
HENUM(sha512, SHA512)
#undef HENUM
MET(OPTIONS)
MET(POST)
MET(HEAD)
MET(PATCH)
MET(DELETE)
MET(GET)
#undef MET
VCL_BLOB tus_b64_decode(VRT_CTX, const char *s, VCL_INT l);
#include "config.h"
#include <dlfcn.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "cache/cache.h"
#include "vsb.h"
#include "vmod_blob.h"
#include "vmod_blobdigest.h"
#include "tus_b64.h"
#include "tus_blob.h"
static const struct Vmod_vmod_blob_Func *vmod_blob = NULL;
static const struct Vmod_vmod_blobdigest_Func *vmod_blobdigest = NULL;
static void *dl_vmod_blob = NULL;
static void *dl_vmod_blobdigest = NULL;
/* ------------------------------------------------------------
* linkage to blob and blobdigest vmods
*/
static void
load_vmod(const struct vmod_data *data, void **hdl, const void **func)
{
const struct vmod_data *d;
void *dlhdl;
char buf[256];
bprintf(buf, "./vmod_cache/_vmod_%s.%s",
data->name, data->file_id);
dlhdl = dlopen(buf, RTLD_LAZY | RTLD_LOCAL);
if (dlhdl == NULL) {
fprintf(stderr, "dlopen vmod_%s: %s\n", data->name, dlerror());
return;
}
bprintf(buf, "Vmod_%s_Data", data->name);
d = dlsym(dlhdl, buf);
if (d == NULL) {
fprintf(stderr, "dlsym vmod_%s: %s\n", data->name, dlerror());
(void) dlclose(dlhdl);
return;
}
if (strcmp(d->name, data->name) ||
strcmp(d->file_id, data->file_id) ||
strcmp(d->abi, data->abi)) {
fprintf(stderr, "vmod_%s: name/id/abi mismatch\n", data->name);
(void) dlclose(dlhdl);
return;
}
*hdl = dlhdl;
*func = d->func;
fprintf(stderr, "vmod_%s OK\n", data->name);
}
#define HENUM(t, b) static struct vmod_blobdigest_digest *digest_ ## t = NULL;
#include "tbl_hash_enum.h"
static unsigned enabled = 0;
static char *hashes = NULL;
int
tus_chksum_init(VRT_CTX) {
struct vsb names[1];
char buf[512];
int first = 1;
AN(VSB_new(names, buf, sizeof buf, VSB_FIXEDLEN));
load_vmod(&import_Vmod_blob_Data, &dl_vmod_blob,
(const void **)&vmod_blob);
load_vmod(&import_Vmod_blobdigest_Data, &dl_vmod_blobdigest,
(const void **)&vmod_blobdigest);
if (vmod_blobdigest != NULL && vmod_blob != NULL)
enabled = 1;
else
return (0);
fprintf(stderr, "enabled %u\n", enabled);
#define HENUM(t, b) do { \
vmod_blobdigest->digest__init(ctx, &digest_ ## t, \
"tus_digest_" #t, *vmod_blobdigest->enum_ ## b, NULL, \
*vmod_blobdigest->enum_TASK); \
if (digest_ ## t == NULL) \
break; \
if (first == 0) \
VSB_cat(names, ","); \
VSB_cat(names, #t); \
first = 0; \
} while (0);
#include "tbl_hash_enum.h"
AZ(VSB_finish(names));
REPLACE(hashes, VSB_data(names));
fprintf(stderr, "digests OK\n");
// dummy ref to appease compiler
memset(&Vmod_vmod_blobdigest_Func, 0, sizeof Vmod_vmod_blobdigest_Func);
memset(&Vmod_vmod_blob_Func, 0, sizeof Vmod_vmod_blob_Func);
return (0);
}
int
tus_chksum_fini(VRT_CTX) {
#define HENUM(t, b) if (digest_ ## t != NULL) \
vmod_blobdigest->digest__fini(&digest_ ## t);
#include "tbl_hash_enum.h"
REPLACE(hashes, NULL);
if (dl_vmod_blob != NULL)
(void) dlclose(dl_vmod_blob);
if (dl_vmod_blobdigest != NULL)
(void) dlclose(dl_vmod_blobdigest);
return (0);
}
/* ------------------------------------------------------------
* wrap base64
*/
VCL_BLOB
tus_b64_decode(VRT_CTX, const char *s, VCL_INT l)
{
struct strands st = {.n = 1, .p = &s};
if (! enabled)
return (NULL);
AN(vmod_blob);
AN(vmod_blob->decode);
AN(vmod_blob->enum_BASE64);
return (vmod_blob->decode(ctx, *vmod_blob->enum_BASE64, l, &st));
}
/* ------------------------------------------------------------
* enabled checksums
*/
const char *
tus_checksums(void)
{
return (enabled ? hashes : NULL);
}
/* ------------------------------------------------------------
* handling of tus checksums
*/
struct tus_chksum {
unsigned magic;
#define VMOD_TUS_CHKSUM_MAGIC 0x105c6650
const struct vmod_blobdigest_digest *digest;
VCL_BLOB expect;
VCL_BLOB final;
};
/* tus hash names to blobdiget object */
const struct vmod_blobdigest_digest *
tus_hash(const char *s, size_t l)
{
if (enabled == 0 || s == NULL)
return (NULL);
if (l == 0)
l = strlen(s);
#define HENUM(t, b) \
if (strncmp(s, #t, l) == 0) return (digest_ ## t);
#include "tbl_hash_enum.h"
return (NULL);
}
struct tus_chksum *
tus_chksum_new(VRT_CTX, const struct vmod_blobdigest_digest *d)
{
struct tus_chksum *r;
AN(d);
r = WS_Alloc(ctx->ws, sizeof *r);
if (r == NULL) {
VRT_fail(ctx, "WS_Alloc failed");
return (NULL);
}
INIT_OBJ(r, VMOD_TUS_CHKSUM_MAGIC);
r->digest = d;
return (r);
}
struct tus_chksum *
tus_chksum_hdr(VRT_CTX, const char *hdr)
{
const struct vmod_blobdigest_digest *d;
struct tus_chksum *r;
VCL_BLOB expect;
const char *sep;
if (enabled == 0 || hdr == NULL)
return (NULL);
sep = strchr(hdr, ' ');
if (sep == NULL)
return (NULL);
d = tus_hash(hdr, (unsigned)(sep - hdr));
if (d == NULL)
return (NULL);
r = tus_chksum_new(ctx, d);
if (r == NULL)
return (NULL);
sep++;
expect = tus_b64_decode(ctx, sep, 0);
if (expect == NULL)
return (NULL);
r->expect = expect;
return (r);
}
void
tus_chksum_update(VRT_CTX, struct tus_chksum *c,
const void *ptr, size_t l)
{
struct vrt_blob b;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(c, VMOD_TUS_CHKSUM_MAGIC);
AN(enabled);
b.type = 0x105;
b.len = l;
b.blob = ptr;
(void) vmod_blobdigest->digest_update(ctx, c->digest, &b);
}
VCL_BLOB
tus_chksum_final(VRT_CTX, struct tus_chksum *c)
{
AN(enabled);
if (c->final == NULL)
c->final = vmod_blobdigest->digest_final(ctx, c->digest);
return (c->final);
}
VCL_BOOL
tus_chksum_equal(VRT_CTX, struct tus_chksum *c)
{
(void) tus_chksum_final(ctx, c);
return (vmod_blob->equal(ctx, c->expect, c->final));
}
// ------------------------------------------------------------
// not using vmod_blob because upload filenames should work without it
static inline char
hexnibble(unsigned char n)
{
return (n < 0xa ? '0' + n : 'a' + n - 0xa);
}
VCL_STRING
tus_hex_buf(char * buf, size_t bufl, VCL_BLOB b)
{
VCL_STRING r = buf;
const unsigned char *p;
unsigned char n;
size_t l;
if (b == NULL || b->len == 0)
return ("");
p = b->blob;
l = b->len;
while (l > 0) {
if (bufl < 3)
return (NULL);
n = *p;
buf[1] = hexnibble(n & 0xf);
n >>= 4;
buf[0] = hexnibble(n & 0xf);
buf += 2;
bufl -= 2;
p++;
l--;
}
AN(bufl);
buf[0] = '\0';
return (r);
}
VCL_STRING
tus_hex(VRT_CTX, VCL_BLOB b)
{
char * buf;
size_t bufl;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
if (b == NULL || b->len == 0)
return ("");
bufl = b->len * 2 + 1;
buf = WS_Alloc(ctx->ws, bufl);
if (buf == NULL)
return (NULL);
return (tus_hex_buf(buf, bufl, b));
}
void
tus_vsbhex(struct vsb *vsb, VCL_BLOB b)
{
size_t l = b->len * 2 + 1;
char buf[l];
VSB_cat(vsb, tus_hex_buf(buf, l, b));
}
int tus_chksum_init(VRT_CTX);
int tus_chksum_fini(VRT_CTX);
const struct vmod_blobdigest_digest *tus_hash(const char *, size_t);
struct tus_chksum *tus_chksum_new(VRT_CTX,
const struct vmod_blobdigest_digest *);
struct tus_chksum *tus_chksum_hdr(VRT_CTX, const char *);
void tus_chksum_update(VRT_CTX, struct tus_chksum *c,
const void *ptr, size_t l);
VCL_BLOB tus_chksum_final(VRT_CTX, struct tus_chksum *c);
VCL_BOOL tus_chksum_equal(VRT_CTX, struct tus_chksum *c);
#define MAX_CONCAT 64
#define READY_TIMEOUT 60 // parameter?
struct tus_concat {
unsigned magic;
#define TUS_CONCAT_MAGIC 0x105c0ca7
unsigned n;
struct tus_file_core *cores[];
// dynamically sized
};
#include "config.h"
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <dirent.h>
#include <errno.h>
#include <time.h>
#include <cache/cache_varnishd.h>
#include <vtim.h>
#include <vrnd.h>
#include "tus_file.h"
#include "tus_blob.h"
#include "tus_hdr.h"
#include "tus_hex.h"
#include "tus_file_exp.h"
VSPLAY_GENERATE(tus_files, tus_file_core, entry, tus_file_cmp);
#define TUS_FILE_PFX "tus_"
#define TUS_FILE_RAND 16
#define TUS_FILE_SUFF "_XXXXXX"
static uintmax_t header_size;
/*
* ------------------------------------------------------------
* specific to TUS_CONCAT
*/
#include "tus_concat.h"
/*
* the the list of concats is just a list of strings terminated by a zero-length
* string
*/
static struct vsb *
tus_file_final_concat_parse(const char *spec)
{
const char *p;
struct vsb *vsb;
vsb = VSB_new_auto();
/* extract url_path */
while (spec != NULL) {
while (*spec == ' ')
spec++;
if (strncmp(spec, "http", 4) == 0) {
spec += 4;
if (*spec == 's')
spec++;
if (strncmp(spec, "://", 3) != 0)
goto err;
spec += 3;
p = strchr(spec, '/');
if (p == NULL)
goto err;
spec = p;
}
if (*spec != '/')
goto err;
p = strchr(spec, ' ');
if (p == NULL)
p = strchr(spec, '\0');
AN(p);
VSB_bcat(vsb, spec, p - spec);
VSB_bcat(vsb, "\0", 1);
spec = strchr(spec, ' ');
}
VSB_bcat(vsb, "\0", 1);
VSB_finish(vsb);
return (vsb);
err:
VSB_destroy(&vsb);
return (NULL);
}
struct concat_embryo *
tus_file_final_concat(struct VPFX(tus_server) *srv,
struct concat_embryo *embryo, const char *spec)
{
struct tus_file_core *part, *parts[MAX_CONCAT];
struct tus_concat *concat;
const struct tus_file_disk *pdisk;
struct vsb *vsb;
unsigned i, n = 0;
struct timespec ts;
ssize_t length = 0;
int errno;
size_t l, ml;
vsb = tus_file_final_concat_parse(spec);
if (vsb == NULL)
return (NULL);
spec = VSB_data(vsb);
tus_server_lock(srv);
while ((l = strlen(spec)) > 0) {
part = tus_file_lookup(srv, spec);
if (part == NULL)
goto lookup_err;
pdisk = part->disk;
if (pdisk == NULL ||
pdisk->type != TUS_PARTIAL)
goto lookup_err;
(void) tus_file_ref(part);
parts[n++] = part;
if (n == MAX_CONCAT)
goto lookup_err;
spec += l;
spec++;
}
tus_server_unlock(srv);
if (n == 0)
goto err;
// wait for parts to become ready
AZ(clock_gettime(CLOCK_REALTIME, &ts));
ts.tv_sec += READY_TIMEOUT;
for (i = 0; i < n; i++) {
part = parts[i];
pdisk = part->disk;
AN(pdisk);
if (part->ptr != NULL) {
length += pdisk->upload_length;
continue;
}
AZ(pthread_mutex_lock(&part->mtx));
errno = EINTR;
while (part->ptr == NULL && errno == EINTR) {
errno = pthread_cond_timedwait(&part->cond,
&part->mtx, &ts);
}
AZ(pthread_mutex_unlock(&part->mtx));
if (part->ptr == NULL)
goto err;
length += pdisk->upload_length;
}
l = n * sizeof(struct tus_file_core *);
ml = sizeof(*concat) + l;
concat = malloc(ml);
AN(concat);
INIT_OBJ(concat, TUS_CONCAT_MAGIC);
concat->n = n;
memcpy(concat->cores, parts, l);
INIT_OBJ(embryo, CONCAT_EMBRYO_MAGIC);
embryo->srv = srv;
embryo->spec_vsb = vsb;
embryo->concat = concat;
embryo->concat_l = ml;
embryo->upload_length = length;
return (embryo);
lookup_err:
tus_server_unlock(srv);
err:
for (i = 0; i < n; i++)
(void) tus_file_unref(parts[i]);
VSB_destroy(&vsb);
return (NULL);
}
struct tus_file_core *
tus_file_final_birth(struct tus_file_core *fcore, struct concat_embryo *embryo)
{
struct VPFX(tus_server) *srv;
struct tus_file_disk *fdisk;
struct vsb *vsb;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
CHECK_OBJ_NOTNULL(embryo, CONCAT_EMBRYO_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
AZ(fcore->ptr);
AZ(fcore->len);
fcore->ptr = embryo->concat;
fcore->len = embryo->concat_l;
fdisk->upload_length = fdisk->upload_offset = embryo->upload_length;
vsb = embryo->spec_vsb;
assert(fcore->fd >= 0);
if (write(fcore->fd, VSB_data(vsb), VSB_len(vsb)) < 0) {
srv = fcore->server;
tus_server_lock(srv);
tus_file_del(&fcore);
tus_server_unlock(srv);
}
VSB_destroy(&vsb);
memset(embryo, 0, sizeof *embryo);
return (fcore);
}
static void
tus_file_concat_fini(struct tus_concat *concat)
{
unsigned i;
for (i = 0; i < concat->n; i++)
(void) tus_file_unref(concat->cores[i]);
free(concat);
}
void
tus_file_final_abort(struct concat_embryo *embryo)
{
CHECK_OBJ_NOTNULL(embryo, CONCAT_EMBRYO_MAGIC);
tus_file_concat_fini(embryo->concat);
VSB_destroy(&embryo->spec_vsb);
memset(embryo, 0, sizeof *embryo);
}
static void
tus_file_final_fini(struct tus_file_core *fcore)
{
struct tus_concat *concat;
if (fcore->ptr == NULL)
return;
CAST_OBJ_NOTNULL(concat, fcore->ptr, TUS_CONCAT_MAGIC);
fcore->ptr = NULL;
assert (fcore->len >= sizeof *concat);
fcore->len = 0;
tus_file_concat_fini(concat);
}
// checksum over all parts
VCL_BLOB
tus_concat_hash(VRT_CTX, const struct VPFX(tus_server) *srv,
const struct tus_concat *concat)
{
const struct vmod_blobdigest_digest *d;
struct tus_chksum *c;
struct tus_file_core *part;
unsigned i;
CHECK_OBJ_NOTNULL(concat, TUS_CONCAT_MAGIC);
d = tus_server_digest(srv);
if (d == NULL)
return (NULL);
c = tus_chksum_new(ctx, d);
if (c == NULL)
return (NULL);
for (i = 0; i < concat->n; i++) {
part = concat->cores[i];
if (part->len == 0)
continue;
AN(part->ptr);
tus_chksum_update(ctx, c, part->ptr, part->len);
}
return (tus_chksum_final(ctx, c));
}
// all parts url
const char *
tus_file_final_urls(VRT_CTX, const struct tus_file_core *fcore, const char *pfx)
{
const struct tus_file_core *part;
const struct tus_file_disk *fdisk;
const struct tus_concat *concat;
struct vsb vsb[1];
unsigned i;
if (fcore->ptr == NULL)
return (NULL);
WS_VSB_new(vsb, ctx->ws);
VSB_cat(vsb, "final;");
CAST_OBJ_NOTNULL(concat, fcore->ptr, TUS_CONCAT_MAGIC);
for (i = 0; i < concat->n; i++) {
part = concat->cores[i];
fdisk = part->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
if (i > 0)
VSB_cat(vsb, " ");
VSB_cat(vsb, pfx);
VSB_cat(vsb, fdisk->url_path);
}
return (WS_VSB_finish(vsb, ctx->ws, NULL));
}
/*
* ------------------------------------------------------------
* generic files
*/
/* ensure file is open */
static int
tus_file_open(struct tus_file_core *fcore)
{
int basefd;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
if (fcore->fd < 0) {
basefd = tus_server_basefd(fcore->server);
fcore->fd = openat(basefd, fcore->filename, O_RDWR | O_CLOEXEC | O_APPEND);
}
return (fcore->fd);
}
/* close */
static void
tus_file_close(struct tus_file_core *fcore)
{
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
if (fcore->fd == -1)
return;
(void) close(fcore->fd);
fcore->fd = -1;
}
static unsigned tus_file_unref_locked(struct tus_file_core *fcore);
void
tus_file_init(void)
{
uintmax_t page_size = getpagesize();
AN(page_size);
header_size = RUP2(sizeof(struct tus_file_disk), page_size);
assert(header_size >= sizeof(struct tus_file_disk));
}
/* returns NULL if exists */
static struct tus_file_core *
tus_file_core_new(struct VPFX(tus_server) *srv,
int fd, const char *filename, struct tus_file_disk *fdisk)
{
struct tus_file_core *fcore;
ALLOC_OBJ(fcore, VMOD_TUS_FILE_CORE_MAGIC);
AN(fcore);
fcore->server = srv;
fcore->fd = fd;
REPLACE(fcore->filename, filename);
AZ(pthread_mutex_init(&fcore->mtx, NULL));
AZ(pthread_cond_init(&fcore->cond, NULL));
fcore->disk = fdisk;
fcore->refcnt = 1;
if (VSPLAY_INSERT(tus_files, tus_server_files(srv), fcore) == NULL) {
tus_exp_insert(fcore);
return (fcore);
}
AZ(pthread_mutex_destroy(&fcore->mtx));
AZ(pthread_cond_destroy(&fcore->cond));
REPLACE(fcore->filename, NULL);
FREE_OBJ(fcore);
return (NULL);
}
static void *
tus_mmap_header(int fd)
{
return (mmap(NULL, header_size, PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_NORESERVE, fd, 0));
}
void
tus_file_mmap(struct tus_file_core *fcore)
{
struct tus_file_disk *fdisk;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_TUS_FILE_DISK(fdisk);
assert(fdisk->upload_length == fdisk->upload_offset);
AZ(fcore->ptr);
AZ(fcore->len);
fcore->ptr = mmap(NULL, fdisk->upload_length, PROT_READ,
MAP_SHARED | MAP_NORESERVE, fcore->fd, header_size);
AN(fcore->ptr);
tus_file_close(fcore);
fcore->len = fdisk->upload_length;
}
static void
tus_file_disk_del(struct tus_file_disk **fdiskp,
int *fdp, const char *filename, int basefd)
{
if (fdiskp != NULL) {
if (*fdiskp != NULL)
(void) munmap(*fdiskp, header_size);
*fdiskp = NULL;
}
if (fdp != NULL) {
if (*fdp >= 0)
(void) close(*fdp);
*fdp = -1;
}
if (filename != NULL) {
if (basefd >= 0)
(void) unlinkat(basefd, filename, 0);
else
(void) unlink(filename);
}
}
/* fini an fcore already removed from tus_files and without references */
static void
tus_file_fini(struct tus_file_core *fcore)
{
struct tus_file_disk *fdisk;
fdisk = fcore->disk;
CHECK_TUS_FILE_DISK(fdisk);
if (fdisk->type == TUS_FINAL) {
tus_file_final_fini(fcore);
} else if (fcore->ptr != NULL) {
(void) munmap(fcore->ptr, fcore->len);
fcore->ptr = NULL;
fcore->len = 0;
}
fcore->disk = NULL;
tus_file_disk_del(&fdisk, &fcore->fd, fcore->filename,
tus_server_basefd(fcore->server));
AZ(fdisk);
assert(fcore->fd == -1);
AZ(pthread_mutex_destroy(&fcore->mtx));
AZ(pthread_cond_destroy(&fcore->cond));
REPLACE(fcore->filename, NULL);
FREE_OBJ(fcore);
}
/* under tus_server mtx to protect tus_files */
void
tus_file_del(struct tus_file_core **fcorep)
{
struct tus_file_core *fcore;
const struct tus_file_core *remove;
TAKE_OBJ_NOTNULL(fcore, fcorep, VMOD_TUS_FILE_CORE_MAGIC);
remove = VSPLAY_REMOVE(tus_files, tus_server_files(fcore->server),
fcore);
assert (remove == fcore);
tus_exp_delete(fcore);
(void) tus_file_unref_locked(fcore);
}
/* close all files for tus_server shutdown */
void
tus_file_shutdown(struct VPFX(tus_server) *srv)
{
struct tus_file_core *fcore;
VSPLAY_FOREACH(fcore, tus_files, tus_server_files(srv)) {
REPLACE(fcore->filename, NULL); // prevent unlink
AZ(pthread_mutex_lock(&fcore->mtx));
tus_file_del(&fcore);
AZ(fcore);
}
}
#ifdef BAD_IDEA
void
tus_file_rename_base(struct tus_file_core *fcore, const char *basename)
{
struct tus_file_disk *fdisk;
const struct tus_file_core *check;
struct VPFX(tus_server) *srv;
struct tus_files *files;
unsigned l;
char *p;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
srv = fcore->server;
files = tus_server_files(fcore->server);
// pessimistic
l = strlen(basename) + strlen(fdisk->url_path);
assert (l < TUS_PATH_MAX);
p = strrchr(fdisk->url_path, '/');
AN(p);
p++;
AN(*p);
l = fdisk->url_path_length + strlen(basename) - strlen(p);
tus_server_lock(srv);
check = VSPLAY_REMOVE(tus_files, files, fcore);
assert(check == fcore);
(void) strcpy(p, basename);
fdisk->url_path_length = l;
AZ(VSPLAY_INSERT(tus_files, files, fcore));
tus_server_unlock(srv);
}
#endif
unsigned
tus_file_ref(struct tus_file_core *fcore)
{
unsigned r;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
AZ(pthread_mutex_lock(&fcore->mtx));
r = fcore->refcnt++;
AZ(pthread_mutex_unlock(&fcore->mtx));
return (r);
}
static unsigned
tus_file_unref_locked(struct tus_file_core *fcore)
{
unsigned r;
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
r = --fcore->refcnt;
AZ(pthread_mutex_unlock(&fcore->mtx));
if (r == 0)
tus_file_fini(fcore);
return (r);
}
unsigned
tus_file_unref(struct tus_file_core *fcore)
{
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
AZ(pthread_mutex_lock(&fcore->mtx));
return (tus_file_unref_locked(fcore));
}
/* under tus_server mtx to protect tus_files */
static void
tus_file_add(struct VPFX(tus_server) *srv, int basefd, const char *filename)
{
struct tus_file_core *fcore;
struct tus_file_disk *fdisk;
struct stat st;
int fd = -1;
const char *err;
if (fstatat(basefd, filename, &st, AT_SYMLINK_NOFOLLOW)) {
fprintf(stderr, "tus add stat %s: %s\n", filename, errno);
goto err;
}
assert(st.st_size >= 0);
if ((uintmax_t)st.st_size < header_size) {
fprintf(stderr, "tus add %s: too small\n", filename);
goto err;
}
fd = openat(basefd, filename, O_RDWR | O_CLOEXEC | O_APPEND);
if (fd < 0) {
fprintf(stderr, "tus add open %s: %s\n", filename, errno);
goto err;
}
fdisk = tus_mmap_header(fd);
if (fdisk == NULL) {
fprintf(stderr, "tus add mmap %s: %s\n", filename, errno);
goto err;
}
/*
* we do not read in final concats because they do not need relevant
* amounts of date for re-upload
*/
if (fdisk->type == TUS_FINAL) {
fprintf(stderr, "tus add %s: is final\n", filename);
goto err;
}
err = tus_file_disk_err(fdisk);
if (err != NULL) {
fprintf(stderr, "tus add %s: %s\n", filename, err);
goto err;
}
if (fdisk->upload_expires < VTIM_real()) {
fprintf(stderr, "tus add %s: expired\n", filename);
goto err;
}
/* note: the close can cause a SIGSEGV if a file is unlinked
* concurrently. But as we should be the only user of the base
* directory, that should not happen
*
* closing makes sense because we are likely not to use all
* files present when starting
*/
fcore = tus_file_core_new(srv, fd, filename, fdisk);
if (fcore != NULL) {
tus_file_close(fcore);
return;
}
fprintf(stderr, "tus add %s: duplicate url_path %s\n",
filename, fdisk->url_path);
err:
tus_file_disk_del(&fdisk, &fd, filename, basefd);
AZ(fdisk);
assert(fd == -1);
}
/* under tus_server mtx to protect tus_files */
void
tus_file_load(struct VPFX(tus_server) *srv)
{
int basefd = tus_server_basefd(srv);
DIR *dir;
struct dirent *ent;
dir = fdopendir(dup(basefd));
AN(dir);
ent = readdir(dir);
while (ent != NULL) {
if (strncmp(ent->d_name, TUS_FILE_PFX, strlen(TUS_FILE_PFX)) == 0 &&
ent->d_type == DT_REG)
tus_file_add(srv, basefd, ent->d_name);
ent = readdir(dir);
}
AZ(closedir(dir));
}
static void
tus_touch(struct tus_file_core *fcore, VCL_DURATION expires)
{
struct tus_file_disk *fdisk;
VCL_TIME t = VTIM_real();
fdisk = fcore->disk;
CHECK_TUS_FILE_DISK(fdisk);
if (fdisk->upload_expires < t)
return;
fdisk->upload_expires = t + expires;
tus_exp_touch(fcore);
}
/* under tus_server mtx to protect tus_files */
struct tus_file_core *
tus_file_lookup(struct VPFX(tus_server) *srv, const char *url_path)
{
struct tus_file_core fcore, *found;
struct tus_file_disk needle;
unsigned l = strlen(url_path);
if (l >= TUS_PATH_MAX) {
errno = ENAMETOOLONG;
return NULL;
}
INIT_OBJ(&fcore, VMOD_TUS_FILE_CORE_MAGIC);
INIT_OBJ(&needle, VMOD_TUS_FILE_DISK_MAGIC);
needle.guard_magic = VMOD_TUS_FILE_DISK_MAGIC;
needle.guard2_magic = VMOD_TUS_FILE_DISK_MAGIC;
needle.version = 1;
strcpy(needle.url_path, url_path);
needle.url_path_length = l;
fcore.disk = &needle;
found = VSPLAY_FIND(tus_files, tus_server_files(srv), &fcore);
if (found == NULL)
return (NULL);
tus_touch(found, tus_server_expires(srv));
return (found);
}
static void
tus_name_rnd(struct vsb *vsb)
{
char rnd[TUS_FILE_RAND];
struct vrt_blob b;
VRND_RandomCrypto(rnd, sizeof rnd);
b.type = 0x1055;
b.blob = rnd;
b.len = sizeof rnd;
tus_vsbhex(vsb, &b);
}
/* under tus_server mtx to protect tus_files */
struct tus_file_core *
tus_file_new(VRT_CTX, struct VPFX(tus_server) *srv, enum tus_f_type type,
const char *url_path, const char *id, const char *metadata)
{
struct tus_file_core *fcore;
struct tus_file_disk *fdisk = NULL;
char buf[PATH_MAX];
struct vsb vsb_path[1];
const char *path;
const char *filename; // relative to base
int fd = -1;
size_t l;
if (id != NULL) {
while (*id == '/')
id++;
l = strlen(id);
} else {
// tus_name_rnd uses tus_hex
l = strlen(TUS_FILE_PFX) + TUS_FILE_RAND * 2 +
strlen(TUS_FILE_SUFF);
}
l++;
if (strlen(url_path) + l > TUS_PATH_MAX) {
errno = ENAMETOOLONG;
VSLb(ctx->vsl, SLT_Error, "%s: path too long: %s",
tus_server_name(srv), url_path);
return (NULL);
}
if (metadata != NULL && strlen(metadata) + 1 > TUS_METADATA_MAX) {
errno = ENAMETOOLONG;
VSLb(ctx->vsl, SLT_Error, "%s: metadata too long: %s",
tus_server_name(srv), metadata);
return (NULL);
}
/* there is no mkostempat() */
AN(VSB_new(vsb_path, buf, sizeof buf, VSB_FIXEDLEN));
VSB_cat(vsb_path, tus_server_basedir(srv));
VSB_cat(vsb_path, "/" TUS_FILE_PFX);
tus_name_rnd(vsb_path);
VSB_cat(vsb_path, TUS_FILE_SUFF);
AZ(VSB_finish(vsb_path));
fd = mkostemp(VSB_data(vsb_path), O_APPEND | O_CLOEXEC);
if (fd < 0) {
VSLb(ctx->vsl, SLT_Error, "%s: mkostemp(%s) failed: %d (%s)",
tus_server_name(srv), VSB_data(vsb_path),
errno, strerror(errno));
return (NULL);
}
path = VSB_data(vsb_path);
if (fallocate(fd, 0, 0, header_size)) {
VSLb(ctx->vsl, SLT_Error, "%s: fallocate(%s) failed: %d (%s)",
tus_server_name(srv), path,
errno, strerror(errno));
goto err;
}
filename = strrchr(path, '/');
AN(filename);
filename++;
AN(*filename);
if (id == NULL)
id = filename;
fdisk = tus_mmap_header(fd);
if (fdisk == NULL) {
VSLb(ctx->vsl, SLT_Error, "%s: tus_mmap_header(%s) failed: %d (%s)",
tus_server_name(srv), path,
errno, strerror(errno));
goto err;
}
INIT_OBJ(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
fdisk->guard_magic = VMOD_TUS_FILE_DISK_MAGIC;
fdisk->guard2_magic = VMOD_TUS_FILE_DISK_MAGIC;
fdisk->version = 1;
fdisk->type = type;
fdisk->url_path_length = strlen(url_path);
AN(fdisk->url_path_length);
strcpy(fdisk->url_path, url_path);
if (url_path[fdisk->url_path_length - 1] != '/')
fdisk->url_path[fdisk->url_path_length++] = '/';
strcpy(fdisk->url_path + fdisk->url_path_length, id);
fdisk->url_path_length += strlen(id);
assert(fdisk->url_path_length < TUS_PATH_MAX);
if (metadata && *metadata != '\0') {
fdisk->metadata_length = strlen(metadata);
assert(fdisk->metadata_length < TUS_METADATA_MAX);
strcpy(fdisk->metadata, metadata);
}
fdisk->upload_length = -1;
fdisk->upload_expires = VTIM_real() + tus_server_expires(srv);
CHECK_TUS_FILE_DISK(fdisk);
fcore = tus_file_core_new(srv, fd, filename, fdisk);
if (fcore != NULL)
return (fcore);
/* undo. happens for clash with custom id */
AN(id);
err:
tus_file_disk_del(&fdisk, &fd, path, -1);
return (NULL);
}
struct tus_suck {
unsigned magic;
#define TUS_SUCK_MAGIC 0x10550c55
VRT_CTX;
VCL_BYTES max;
int fd;
ssize_t *upload_offset;
struct tus_chksum *chksum;
};
static int v_matchproto_(objiterate_f)
tus_suck_f(void *priv, unsigned flush, const void *ptr, ssize_t len)
{
struct tus_suck *suck;
ssize_t l;
CAST_OBJ_NOTNULL(suck, priv, TUS_SUCK_MAGIC);
// we could use writev(), but unclear if it is worth it
(void) flush;
if (len == 0)
return (0);
assert(len > 0);
l = write(suck->fd, ptr, len);
if (l != len)
l = -1;
if (suck->chksum)
tus_chksum_update(suck->ctx, suck->chksum, ptr, len);
if (l <= 0)
return (l);
*suck->upload_offset += l;
if (*suck->upload_offset > suck->max) {
errno = EFBIG;
return (-1);
}
return (l);
}
unsigned
tus_body_to_file(VRT_CTX, struct tus_file_core *fcore)
{
struct tus_suck suck;
const char *hdr;
ssize_t offset_saved, written;
struct tus_file_disk *fdisk;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(ctx->req, REQ_MAGIC);
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
INIT_OBJ(&suck, TUS_SUCK_MAGIC);
offset_saved = fcore->disk->upload_offset;
suck.ctx = ctx;
suck.max = tus_server_max(fcore->server);
if (fdisk->upload_length != -1 &&
fdisk->upload_length < suck.max)
suck.max = fdisk->upload_length;
suck.fd = tus_file_open(fcore);
assert(suck.fd >= 0);
suck.upload_offset = &fdisk->upload_offset;
if (http_GetHdr(ctx->http_req, hdr_chksum, &hdr)) {
suck.chksum = tus_chksum_hdr(ctx, hdr);
if (suck.chksum == NULL)
return (400);
}
written = VRB_Iterate(ctx->req->wrk, ctx->vsl, ctx->req,
tus_suck_f, &suck);
if (written < 0 && errno == EFBIG) {
fdisk->upload_offset = fdisk->upload_length = suck.max;
AZ(ftruncate(fcore->fd, header_size + suck.max));
AZ(fdatasync(fcore->fd));
return (413);
}
AZ(fdatasync(fcore->fd));
if (suck.chksum == NULL) {
if (written >= 0)
return (204);
else
return (408); // Timeout
}
if (tus_chksum_equal(ctx, suck.chksum))
return (204);
fcore->disk->upload_offset = offset_saved;
AZ(ftruncate(fcore->fd, header_size + offset_saved));
AZ(fdatasync(fcore->fd));
return (460);
}
VCL_BOOL
tus_file_done(struct tus_file_core *fcore, struct tus_file_disk *fdisk, const char *url)
{
int fd;
size_t l = strlen(url);
if (l >= TUS_METADATA_MAX)
return (0);
assert(fdisk->type == TUS_SINGLE || fdisk->type == TUS_FINAL);
strcpy(fdisk->metadata, url);
fdisk->metadata_length = l;
fdisk->upload_offset = 0;
fdisk->upload_length = 0;
fdisk->type = TUS_REDIRECT;
fd = tus_file_open(fcore);
if (fd >= 0) {
AZ(ftruncate(fd, header_size));
AZ(fdatasync(fd));
tus_file_close(fcore);
}
return (1);
}
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <vtree.h>
#include <vtim.h>
#include "tus_server.h"
#define TUS_PATH_MAX PATH_MAX
#define TUS_METADATA_MAX 2048u // ballpark of AWS S3 metadata
enum tus_f_type {
TUS_SINGLE = 0,
TUS_PARTIAL,
TUS_FINAL,
TUS_REDIRECT
};
// header of disk file - mmaped
struct tus_file_disk {
unsigned magic;
#define VMOD_TUS_FILE_DISK_MAGIC 0x105f11ed
unsigned version;
char url_path[TUS_PATH_MAX];
unsigned guard_magic;
unsigned url_path_length;
// for TUS_REDIRECT, repurposed for location
char metadata[TUS_METADATA_MAX];
unsigned guard2_magic;
unsigned metadata_length;
ssize_t upload_length; // -1 == defer
ssize_t upload_offset;
VCL_TIME upload_expires;
enum tus_f_type type;
};
struct tus_file_core {
unsigned magic;
#define VMOD_TUS_FILE_CORE_MAGIC 0x105f11e0
int fd;
char *filename; // at basedir
struct VPFX(tus_server) *server;
VSPLAY_ENTRY(tus_file_core) entry;
pthread_mutex_t mtx;
pthread_cond_t cond;
struct tus_file_disk *disk;
unsigned refcnt;
unsigned exp_idx;
/* final mmap or concats list */
void *ptr;
size_t len;
};
static inline const char *
tus_file_disk_err(const struct tus_file_disk *d)
{
if (d->magic != VMOD_TUS_FILE_DISK_MAGIC)
return ("bad magic");
if (d->guard_magic != VMOD_TUS_FILE_DISK_MAGIC)
return ("bad guard_magic");
if (d->guard2_magic != VMOD_TUS_FILE_DISK_MAGIC)
return ("bad guard2_magic");
if (d->version != 1)
return ("version != 1");
if (strnlen(d->url_path, TUS_PATH_MAX) != d->url_path_length)
return ("url_path_length mismatch");
if (strnlen(d->metadata, TUS_METADATA_MAX) != d->metadata_length)
return ("metadata_length mismatch");
return (NULL);
}
#define CHECK_TUS_FILE_DISK(x) do { \
const char *err; \
AN(x); \
err = tus_file_disk_err(x); \
if (err != NULL) \
WRONG(err); \
} while(0);
static inline int
tus_file_cmp(const struct tus_file_core *a,
const struct tus_file_core *b) {
const struct tus_file_disk *aa, *bb;
CHECK_OBJ_NOTNULL(a, VMOD_TUS_FILE_CORE_MAGIC);
CHECK_OBJ_NOTNULL(b, VMOD_TUS_FILE_CORE_MAGIC);
aa = a->disk;
bb = b->disk;
CHECK_TUS_FILE_DISK(aa);
CHECK_TUS_FILE_DISK(bb);
return (strcmp(aa->url_path, bb->url_path));
};
VSPLAY_HEAD(tus_files, tus_file_core);
VSPLAY_PROTOTYPE(tus_files, tus_file_core, entry, tus_file_cmp);
// tus_file.c
void tus_file_init(void);
void tus_file_load(struct VPFX(tus_server) *);
void tus_file_shutdown(struct VPFX(tus_server) *srv);
void tus_file_del(struct tus_file_core **);
struct tus_file_core *tus_file_new(VRT_CTX, struct VPFX(tus_server) *,
enum tus_f_type, const char *, const char *, const char *);
struct tus_file_core *tus_file_lookup(struct VPFX(tus_server) *, const char *);
unsigned tus_body_to_file(VRT_CTX, struct tus_file_core *);
void tus_file_mmap(struct tus_file_core *);
unsigned tus_file_ref(struct tus_file_core *);
unsigned tus_file_unref(struct tus_file_core *);
#ifdef BAD_IDEA
void
tus_file_rename_base(struct tus_file_core *fcore, const char *basename);
#endif
const char *
tus_file_final_urls(VRT_CTX, const struct tus_file_core *fcore,
const char *pfx);
struct concat_embryo {
unsigned magic;
#define CONCAT_EMBRYO_MAGIC 0x150c05e5
// to be written to fcore->fd once we have it
struct VPFX(tus_server) *srv;
struct vsb *spec_vsb;
struct tus_concat *concat;
size_t concat_l;
ssize_t upload_length;
};
struct concat_embryo * tus_file_final_concat(struct VPFX(tus_server) *srv,
struct concat_embryo *embryo, const char *spec);
VCL_BLOB tus_concat_hash(VRT_CTX, const struct VPFX(tus_server) *,
const struct tus_concat *);
struct tus_file_core *
tus_file_final_birth(struct tus_file_core *fcore, struct concat_embryo *embryo);
void
tus_file_final_abort(struct concat_embryo *embryo);
VCL_BOOL
tus_file_done(struct tus_file_core *, struct tus_file_disk *, const char *);
#include "config.h"
#include <pthread.h>
#include <math.h>
#include "vdef.h"
#include "vrt.h"
#include "miniobj.h"
#include "vas.h"
#include "vbh.h"
#include "tus_file.h"
#include "tus_file_exp.h"
struct tus_exp {
unsigned magic;
#define TUS_EXP_MAGIC 0x105e8900
unsigned die;
struct vbh *heap;
pthread_mutex_t mtx;
pthread_cond_t cond;
pthread_t thread;
};
static inline vtim_real
fcore_when(const void *p)
{
const struct tus_file_core *fcore;
const struct tus_file_disk *fdisk;
CAST_OBJ_NOTNULL(fcore, p, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
return (fdisk->upload_expires);
}
void *
tus_exp_thread(void *p)
{
struct VPFX(tus_server) *srv;
struct tus_file_core *fcore;
struct timespec ts;
struct tus_exp *e;
vtim_real now, when, t;
CAST_OBJ_NOTNULL(e, p, TUS_EXP_MAGIC);
pthread_mutex_lock(&e->mtx);
while (! e->die) {
fcore = VBH_root(e->heap);
if (fcore == NULL) {
pthread_cond_wait(&e->cond, &e->mtx);
continue;
}
when = fcore_when(fcore);
now = VTIM_real();
if (when > now) {
assert(when > 1e9);
ts.tv_nsec = (long)(modf(when, &t) * 1e9);
ts.tv_sec = (long)t;
assert(ts.tv_nsec >= 0 && ts.tv_nsec < 999999999);
errno = pthread_cond_timedwait(&e->cond, &e->mtx, &ts);
assert(errno == 0 || errno == ETIMEDOUT ||
errno == EINTR);
continue;
}
pthread_mutex_unlock(&e->mtx);
srv = fcore->server;
tus_server_lock(srv);
AZ(pthread_mutex_lock(&fcore->mtx));
tus_file_del(&fcore);
tus_server_unlock(srv);
pthread_mutex_lock(&e->mtx);
}
pthread_mutex_unlock(&e->mtx);
}
void
tus_exp_delete(const struct tus_file_core *fcore)
{
struct tus_exp *e = tus_server_exp(fcore->server);
CHECK_OBJ_NOTNULL(e, TUS_EXP_MAGIC);
pthread_mutex_lock(&e->mtx);
assert(fcore->exp_idx != VBH_NOIDX);
VBH_delete(e->heap, fcore->exp_idx);
pthread_mutex_unlock(&e->mtx);
}
void
tus_exp_insert(struct tus_file_core *fcore)
{
struct tus_exp *e = tus_server_exp(fcore->server);
CHECK_OBJ_NOTNULL(e, TUS_EXP_MAGIC);
pthread_mutex_lock(&e->mtx);
VBH_insert(e->heap, fcore);
if (VBH_root(e->heap) == fcore)
pthread_cond_signal(&e->cond);
pthread_mutex_unlock(&e->mtx);
}
void
tus_exp_touch(const struct tus_file_core *fcore)
{
struct tus_exp *e = tus_server_exp(fcore->server);
CHECK_OBJ_NOTNULL(e, TUS_EXP_MAGIC);
pthread_mutex_lock(&e->mtx);
VBH_reorder(e->heap, fcore->exp_idx);
pthread_mutex_unlock(&e->mtx);
}
static int
tus_exp_cmp(void *priv, const void *a, const void *b) {
(void) priv;
return (fcore_when(a) < fcore_when(b));
}
static void
tus_exp_update(void *priv, void *p, unsigned u)
{
struct tus_file_core *fcore;
(void) priv;
CAST_OBJ_NOTNULL(fcore, p, VMOD_TUS_FILE_CORE_MAGIC);
fcore->exp_idx = u;
}
struct tus_exp *
tus_file_exp_new(void)
{
struct tus_exp *e;
pthread_attr_t attr;
ALLOC_OBJ(e, TUS_EXP_MAGIC);
AN(e);
e->heap = VBH_new(NULL, tus_exp_cmp, tus_exp_update);
AN(e->heap);
AZ(pthread_mutex_init(&e->mtx, NULL));
AZ(pthread_cond_init(&e->cond, NULL));
AZ(pthread_attr_init(&attr));
AZ(pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN));
AZ(pthread_create(&e->thread, &attr, tus_exp_thread, e));
AZ(pthread_attr_destroy(&attr));
return (e);
}
void
tus_file_exp_destroy(struct tus_exp **ep)
{
struct tus_exp *e;
TAKE_OBJ_NOTNULL(e, ep, TUS_EXP_MAGIC);
AN(e->heap);
AZ(VBH_root(e->heap));
e->die = 1;
AZ(pthread_cond_signal(&e->cond));
AZ(pthread_join(e->thread, NULL));
AZ(pthread_cond_destroy(&e->cond));
AZ(pthread_mutex_destroy(&e->mtx));
free(e);
}
void tus_exp_delete(const struct tus_file_core *fcore);
void tus_exp_insert(struct tus_file_core *fcore);
void tus_exp_touch(const struct tus_file_core *fcore);
const char * const hdr_resum = "\016Tus-Resumable:";
const char * const hdr_vers = "\014Tus-Version:";
const char * const hdr_ext = "\016Tus-Extension:";
const char * const hdr_chkalg = "\027Tus-Checksum-Algorithm:";
const char * const hdr_chksum = "\020Upload-Checksum:";
const char * const hdr_concat = "\016Upload-Concat:";
const char * const hdr_max = "\015Tus-Max-Size:";
const char * const hdr_defer = "\024Upload-Defer-Length:";
const char * const hdr_meta = "\020Upload-Metadata:";
const char * const hdr_exp = "\017Upload-Expires:";
const char * const hdr_loc = "\011Location:";
const char * const hdr_method = "\027X-HTTP-Method-Override:";
const char * const hdr_off = "\016Upload-Offset:";
const char * const hdr_len = "\016Upload-Length:";
const char * const hdr_origin = "\007Origin:";
const char * const hdr_acah = "\035Access-Control-Allow-Headers:";
const char * const hdr_acam = "\035Access-Control-Allow-Methods:";
const char * const hdr_acma = "\027Access-Control-Max-Age:";
const char * const hdr_acao = "\034Access-Control-Allow-Origin:";
const char * const hdr_aceh = "\036Access-Control-Expose-Headers:";
const char * const hdr_allow = "\006Allow:";
const char * const hdr_vtc = "\015VTC-Filename:";
const char * const hdr_resum;
const char * const hdr_vers;
const char * const hdr_ext;
const char * const hdr_concat;
const char * const hdr_chkalg;
const char * const hdr_chksum;
const char * const hdr_max;
const char * const hdr_defer;
const char * const hdr_meta;
const char * const hdr_exp;
const char * const hdr_loc;
const char * const hdr_method;
const char * const hdr_off;
const char * const hdr_len;
const char * const hdr_origin;
const char * const hdr_acah;
const char * const hdr_acam;
const char * const hdr_acma;
const char * const hdr_acao;
const char * const hdr_aceh;
const char * const hdr_allow;
const char * const hdr_vtc;
VCL_STRING tus_hex_buf(char *, size_t, VCL_BLOB);
VCL_STRING tus_hex(VRT_CTX, VCL_BLOB);
void tus_vsbhex(struct vsb *, VCL_BLOB);
#include "config.h"
#include <inttypes.h>
#include <unistd.h>
#include <time.h>
#include <cache/cache.h>
#include <vsb.h>
#include "vcc_tus_if.h"
#include "tus_file.h"
#include "tus_servers.h"
#include "tus_hdr.h"
#include "tus_response.h"
#include "tus_request.h"
#include "tus_stv.h"
#include "tus_hex.h"
/* ------------------------------------------------------------
*/
enum met_e {
_INVALID = 0,
#define MET(x) x,
#include "tbl_method.h"
_MET_MAX
};
static enum met_e
parse_met(const char *s)
{
#define MET(x) if (strcmp(s, #x) == 0) return (x);
#include "tbl_method.h"
return (_INVALID);
}
// everything for a tus upload to the backend is complete
static VCL_BOOL
tus_request_complete(VRT_CTX, const struct VPFX(tus_server) *srv,
struct tus_concat *c, struct tus_file_disk *fdisk)
{
VCL_BLOB hash = NULL;
struct vsb vsb[1];
const char *slash;
tus_body_assign(ctx->req, c);
http_ForceField(ctx->http_req, HTTP_HDR_METHOD, "PUT");
if (fdisk->type == TUS_SINGLE)
hash = tus_concat_hash(ctx, srv, c);
if (hash != NULL) {
slash = strrchr(fdisk->url_path, '/');
AN(slash);
WS_VSB_new(vsb, ctx->ws);
VSB_bcat(vsb, fdisk->url_path, (slash - fdisk->url_path) + 1);
tus_vsbhex(vsb, hash);
http_ForceField(ctx->http_req, HTTP_HDR_URL,
WS_VSB_finish(vsb, ctx->ws, NULL));
} else {
http_ForceField(ctx->http_req, HTTP_HDR_URL, fdisk->url_path);
}
http_Unset(ctx->http_req, H_Content_Length);
http_PrintfHeader(ctx->http_req, "Content-Length: %zu",
fdisk->upload_length);
// move to table?
http_Unset(ctx->http_req, hdr_resum);
http_Unset(ctx->http_req, hdr_vers);
http_Unset(ctx->http_req, hdr_ext);
http_Unset(ctx->http_req, hdr_chkalg);
http_Unset(ctx->http_req, hdr_chksum);
http_Unset(ctx->http_req, hdr_max);
http_Unset(ctx->http_req, hdr_defer);
http_Unset(ctx->http_req, hdr_meta);
http_Unset(ctx->http_req, hdr_exp);
http_Unset(ctx->http_req, hdr_loc);
http_Unset(ctx->http_req, hdr_method);
http_Unset(ctx->http_req, hdr_off);
http_Unset(ctx->http_req, hdr_len);
return (1);
}
struct test_meta {
struct test_meta *prev;
const char *k;
size_t l;
};
static VCL_BOOL
tus_meta_key_unique(struct test_meta *this, size_t l)
{
struct test_meta *prev = this;
if (l == 0)
return (0);
if (this->l == 0)
this->l = l;
while ((prev = prev->prev) != NULL) {
if (l != prev->l || strncmp(this->k, prev->k, l) != 0)
continue;
return (0);
}
return (1);
}
// from vmod_blob base64.c
#define ILL ((int8_t) 127)
#define PAD ((int8_t) 126)
const int8_t i64[256] = {
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, 62, ILL, ILL, ILL, 63, /* +, - */
52, 53, 54, 55, 56, 57, 58, 59, /* 0 - 7 */
60, 61, ILL, ILL, ILL, PAD, ILL, ILL, /* 8, 9, = */
ILL, 0, 1, 2, 3, 4, 5, 6, /* A - G */
7, 8, 9, 10, 11, 12, 13, 14, /* H - O */
15, 16, 17, 18, 19, 20, 21, 22, /* P - W */
23, 24, 25, ILL, ILL, ILL, ILL, ILL, /* X, Y, Z */
ILL, 26, 27, 28, 29, 30, 31, 32, /* a - g */
33, 34, 35, 36, 37, 38, 39, 40, /* h - o */
41, 42, 43, 44, 45, 46, 47, 48, /* p - w */
49, 50, 51, ILL, ILL, ILL, ILL, ILL, /* x, y, z */
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL,
ILL, ILL, ILL, ILL, ILL, ILL, ILL, ILL
};
static VCL_BOOL
tus_metadata_validate(const char *s, struct test_meta *prev)
{
struct test_meta this = {prev, s, 0};
// key
while (1) {
if (*s != ',' && *s != ' ' && *s != '\0') {
s++;
continue;
}
if (tus_meta_key_unique(&this, s - this.k) == 0)
return (0);
if (*s == '\0')
return (1);
if (*s == ',' || (*s == ' ' && s[1] == ','))
return (tus_metadata_validate(s + 1, &this));
if (*s == ' ') {
s++;
break;
}
}
while (*s != '\0') {
if (*s == ',')
return (tus_metadata_validate(s + 1, &this));
if (i64[*s] == ILL)
return (0);
s++;
}
return (1);
}
VCL_BOOL
tus_request(VRT_CTX, struct VPFX(tus_server) *tussrv,
struct tus_response *r, const char *url, const char *id)
{
struct tus_file_core *fcore;
struct tus_file_disk *fdisk = NULL;
const char *p, *metadata = NULL, *concat = NULL;
struct concat_embryo embryo;
char *q;
enum met_e m;
int ct_ok, lock = EINVAL;
uintmax_t cl, off = UINTMAX_MAX, len = UINTMAX_MAX;
enum tus_f_type type = TUS_SINGLE;
struct tus_concat *c;
VCL_BLOB hash;
AZ(r->status);
AZ(r->fcore);
if (http_GetHdr(ctx->http_req, hdr_method, &p)) {
http_ForceField(ctx->http_req, HTTP_HDR_METHOD, p);
http_Unset(ctx->http_req, hdr_method);
}
if (http_GetHdr(ctx->http_req, hdr_meta, &metadata) &&
tus_metadata_validate(metadata, NULL) == 0) {
VSLb(ctx->vsl, SLT_Error, "%s: bad metadata format",
tus_server_name(tussrv));
r->status = 400;
return (0);
}
m = parse_met(http_GetMethod(ctx->http_req));
(void) http_GetHdr(ctx->http_req, hdr_origin, &r->origin);
if (m == _INVALID) {
r->status = 405;
return (0);
}
if (m == OPTIONS) {
r->status = 204 + (s_OPTIONS * s_MULT);
return (0);
}
if (http_GetHdr(ctx->http_req, hdr_concat, &concat)) {
if (strcmp(concat, "partial") == 0) {
type = TUS_PARTIAL;
} else if (strncmp(concat, "final;", 6) == 0) {
type = TUS_FINAL;
concat += 6;
} else {
r->status = 400;
return (0);
}
}
if (m == POST && type == TUS_FINAL) {
if (tus_file_final_concat(tussrv, &embryo, concat) == NULL) {
r->status = 400;
return (0);
}
hash = tus_concat_hash(ctx, embryo.srv, embryo.concat);
if (hash != NULL) {
id = tus_hex(ctx, hash);
if (id == NULL) {
r->status = 503;
return (0);
}
}
}
AZ(pthread_mutex_lock(&tussrv->mtx));
if (m == POST) {
fcore = tus_file_new(ctx, tussrv, type, url, id, metadata);
} else {
fcore = tus_file_lookup(tussrv, url);
}
if (fcore != NULL)
r->schemeauth = WS_Copy(ctx->ws, tussrv->schemeauth, -1);
if (fcore != NULL)
lock = pthread_mutex_trylock(&fcore->mtx);
AZ(pthread_mutex_unlock(&tussrv->mtx));
if (fcore != NULL) {
fdisk = fcore->disk;
AN(fdisk);
type = fdisk->type;
}
// https://github.com/tus/tus-resumable-upload-protocol/issues/146
if (fcore == NULL) {
assert (lock == EINVAL);
} else if (lock == EBUSY) {
r->status = 423;
return (0);
} else {
assert (lock == 0);
}
if (m == POST && type == TUS_FINAL) {
if (fcore == NULL) {
tus_file_final_abort(&embryo);
} else {
fcore = tus_file_final_birth(fcore, &embryo);
r->status = 201;
}
}
if (m == DELETE) {
if (fcore == NULL) {
r->status = 404;
} else {
AZ(pthread_mutex_lock(&tussrv->mtx));
tus_file_del(&fcore);
AZ(pthread_mutex_unlock(&tussrv->mtx));
r->fcore = NULL;
r->status = 204;
}
return (0);
}
if (type == TUS_REDIRECT) {
r->fcore = fcore;
r->status = 301;
return (0);
} else if (m == GET) {
r->status = 400;
return (0);
}
r->fcore = fcore;
if (m == HEAD) {
r->status = fcore ? 200 : 404;
return (0);
}
if (fcore == NULL) {
r->status = (m == POST) ? 409 : 404;
return (0);
}
AN(fcore);
AN(fdisk);
ct_ok = http_GetHdr(ctx->http_req, H_Content_Type, &p) &&
(strcmp(p, "application/offset+octet-stream") == 0);
if (http_GetHdr(ctx->http_req, hdr_off, &p)) {
off = strtoumax(p, &q, 10);
if (q == NULL || *q != '\0')
off = UINTMAX_MAX;
}
if (http_GetHdr(ctx->http_req, hdr_len, &p)) {
if (type == TUS_FINAL) {
r->status = 400;
return (0);
}
len = strtoumax(p, &q, 10);
if (q == NULL || *q != '\0')
len = UINTMAX_MAX;
}
if (http_GetHdr(ctx->http_req, hdr_defer, &p)) {
if (len != UINTMAX_MAX || type == TUS_FINAL) {
r->status = 400;
return (0);
}
if (strcmp(p, "1") != 0) {
r->status = 409; // or 400 ?
return (0);
}
}
if (len != UINTMAX_MAX) {
if (fdisk->upload_length == -1)
fdisk->upload_length = len;
else if (len != fdisk->upload_length) {
r->status = 409; // or 400 ?
return (0);
}
}
if (type == TUS_FINAL) {
if (m != POST) {
r->status = 403;
r->fcore = NULL;
return (0);
}
return (tus_request_complete(ctx, tussrv, fcore->ptr, fdisk));
}
assert (type != TUS_FINAL);
switch (m) {
case PATCH: {
if (! ct_ok) {
r->status = 415;
return (0);
}
if (fcore == NULL) {
r->status = 404;
return (0);
}
if (off != fdisk->upload_offset) {
r->status = 409;
return (0);
}
r->status = tus_body_to_file(ctx, fcore);
break;
}
case POST: {
cl = 0;
if (http_GetHdr(ctx->http_req, H_Content_Length, &p))
cl = strtoumax(p, &q, 10);
if (cl && ! ct_ok) {
r->status = 415;
return (0);
}
AZ(fdisk->upload_offset);
if (cl == 0) {
r->status = 201;
break;
}
r->status = tus_body_to_file(ctx, fcore);
if (r->status != 204)
break;
r->status = 201;
break;
}
default:
INCOMPL();
}
if (r->status == 413) {
AZ(pthread_mutex_lock(&tussrv->mtx));
tus_file_del(&fcore);
AZ(pthread_mutex_unlock(&tussrv->mtx));
r->fcore = NULL;
return (0);
}
if (fdisk->upload_offset == fdisk->upload_length) {
tus_file_mmap(fcore);
AZ(pthread_cond_signal(&fcore->cond));
if (type == TUS_PARTIAL)
return (0);
assert (type == TUS_SINGLE);
c = tus_body_single(ctx->req, fcore);
if (c == NULL) {
VRT_fail(ctx, "not enough workspace");
return (0);
}
return (tus_request_complete(ctx, tussrv, c, fdisk));
}
return (0);
}
VCL_BOOL
tus_request(VRT_CTX, struct VPFX(tus_server) *tussrv,
struct tus_response *r, const char *url, const char *id);
#include "config.h"
#include <sys/statvfs.h>
#include <cache/cache.h>
#include <vrt_obj.h>
#include "vcc_tus_if.h"
#include "tus_b64.h"
#include "tus_file.h"
#include "tus_servers.h"
#include "tus_hdr.h"
#include "tus_response.h"
/* tus_blob.c */
const char * tus_checksums(void);
const unsigned s_MULT = 1000;
const unsigned s_OPTIONS = 1;
const unsigned s_HEAD = 2;
const char * const allow = "POST, GET, HEAD, PATCH, DELETE, OPTIONS";
static VCL_BYTES
tus_upload_length(const struct VPFX(tus_server) *tussrv,
const struct tus_file_core *fcore)
{
VCL_BYTES max;
struct statvfs fs;
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if (fcore != NULL && fcore->fd >= 0) {
if (fstatvfs(fcore->fd, &fs))
return (tussrv->max);
} else if (statvfs(tussrv->basedir, &fs))
return (tussrv->max);
max = fs.f_bavail * fs.f_frsize;
return (max < tussrv->max ? max : tussrv->max);
}
static void
tus_cors(VCL_HTTP r, unsigned status, const char *origin)
{
http_ForceHeader(r, hdr_acao, origin);
// from tus go server pkg/handler/unrouted_handler.go
if (status / s_MULT == s_OPTIONS) {
http_ForceHeader(r, hdr_acam, allow);
http_ForceHeader(r, hdr_acah,
"Authorization, Origin, X-Requested-With, X-Request-ID, "
"X-HTTP-Method-Override, Content-Type, Upload-Length, "
"Upload-Offset, Tus-Resumable, Upload-Metadata, "
"Upload-Defer-Length, Upload-Concat");
http_ForceHeader(r, hdr_acma, "86400");
} else {
http_ForceHeader(r, hdr_aceh,
"Upload-Offset, Location, Upload-Length, Tus-Version, "
"Tus-Resumable, Tus-Max-Size, Tus-Extension, "
"Upload-Metadata, Upload-Defer-Length, Upload-Concat");
}
}
void
tus_response(VRT_CTX, const struct VPFX(tus_server) *tussrv,
struct tus_response *resp)
{
const struct tus_file_disk *fdisk = NULL;
const struct tus_file_core *fcore = NULL;
const char *chksums = tus_checksums();
VCL_HTTP r;
char t[VTIM_FORMAT_SIZE];
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
CHECK_OBJ_NOTNULL(resp, VMOD_TUS_RESPONSE_MAGIC);
if (resp->fcore) {
fcore = resp->fcore;
fdisk = fcore->disk;
AN(fdisk);
}
CHECK_OBJ_ORNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
r = ctx->http_resp;
CHECK_OBJ_NOTNULL(r, HTTP_MAGIC);
http_Unset(r, hdr_max);
http_Unset(r, hdr_off);
http_Unset(r, hdr_len);
http_Unset(r, hdr_defer);
http_Unset(r, hdr_meta);
http_Unset(r, hdr_exp);
http_Unset(r, hdr_loc);
if (resp->status == 301) {
AN(fdisk);
if (fdisk->metadata_length == 0)
resp->status = 410;
else if (fdisk->metadata[0] == '/')
http_PrintfHeader(r, "Location: %s%s", resp->schemeauth,
fdisk->metadata);
else
http_ForceHeader(r, hdr_loc, fdisk->metadata);
VRT_l_resp_status(ctx, resp->status);
return;
}
VRT_l_resp_status(ctx, resp->status);
if (resp->status == 405) {
http_ForceHeader(r, hdr_allow, allow);
return;
}
http_ForceHeader(r, hdr_resum, "1.0.0");
http_ForceHeader(r, hdr_vers, "1.0.0");
if (chksums != NULL) {
http_ForceHeader(r, hdr_ext, "creation,creation-with-upload,"
"expiration,termination,concatenation,checksum");
http_ForceHeader(r, hdr_chkalg, chksums);
} else {
http_ForceHeader(r, hdr_ext, "creation,creation-with-upload,"
"expiration,termination,concatenation");
}
if (resp->origin != NULL && *resp->origin != '\0')
tus_cors(r, resp->status, resp->origin);
if (tussrv) {
http_PrintfHeader(r, "Tus-Max-Size: %ju",
tus_upload_length(tussrv, fcore));
}
while (fdisk) {
AN(fcore);
if (fdisk->upload_offset >= 0)
http_PrintfHeader(r, "Upload-Offset: %zi",
fdisk->upload_offset);
if (fdisk->upload_length >= 0)
http_PrintfHeader(r, "Upload-Length: %zi",
fdisk->upload_length);
else if (fdisk->upload_length == -1)
http_ForceHeader(r, hdr_defer, "1");
else
INCOMPL();
if (fdisk->metadata_length != 0)
http_ForceHeader(r, hdr_meta, fdisk->metadata);
VTIM_format(fdisk->upload_expires, t);
http_ForceHeader(r, hdr_exp, t);
if (resp->status == 201) {
AN(resp->schemeauth);
http_PrintfHeader(r, "Location: %s%s", resp->schemeauth,
fdisk->url_path);
}
switch (fdisk->type) {
case TUS_SINGLE:
break;
case TUS_FINAL:
if (fcore->ptr == NULL)
break;
AN(resp->schemeauth);
http_ForceHeader(r, hdr_concat,
tus_file_final_urls(ctx,
resp->fcore, resp->schemeauth));
break;
case TUS_PARTIAL:
http_ForceHeader(r, hdr_concat, "partial");
break;
}
break;
}
}
VCL_BOOL
tus_done(VRT_CTX, const struct VPFX(tus_server) *tussrv,
const struct tus_response *resp, const char *url)
{
struct tus_file_core *fcore;
struct tus_file_disk *fdisk;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
CHECK_OBJ_NOTNULL(resp, VMOD_TUS_RESPONSE_MAGIC);
fcore = resp->fcore;
if (fcore == NULL)
return (0);
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
if (fdisk->type != TUS_SINGLE && fdisk->type != TUS_FINAL)
return (0);
return (tus_file_done(fcore, fdisk, url));
}
static inline const char *
tus_meta_find(const char *meta, const char *key, size_t l,
const char **vp, size_t *vlp)
{
const char *v, *ve;
while (meta != NULL && *meta != '\0') {
if (strncmp(meta, key, l) == 0) {
v = meta + l;
if (*v == '\0' || *v == ',' ||
(v[0] == ' ' && v[1] == ','))
return (meta);
if (*v == ' ') {
v++;
if (vp != NULL)
*vp = v;
if (vlp != NULL) {
ve = strchr(v, ',');
if (ve != NULL)
*vlp = ve - v;
else
*vlp = strlen(v);
}
return (meta);
}
}
meta = strchr(meta, ',');
if (meta != NULL)
meta++;
}
return (NULL);
}
VCL_BOOL
tus_meta(VRT_CTX, const struct tus_response *resp, const char *k, VCL_BLOB *b)
{
const struct tus_file_core *fcore;
const struct tus_file_disk *fdisk;
const char *meta, *v = NULL;
size_t kl = strlen(k), vl = 0;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(resp, VMOD_TUS_RESPONSE_MAGIC);
fcore = resp->fcore;
if (fcore == NULL)
return (0);
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
fdisk = fcore->disk;
CHECK_OBJ_NOTNULL(fdisk, VMOD_TUS_FILE_DISK_MAGIC);
meta = fdisk->metadata;
if (b == NULL)
return (tus_meta_find(meta, k, kl, NULL, NULL) != NULL);
meta = tus_meta_find(meta, k, kl, &v, &vl);
if (meta == NULL)
return (0);
*b = tus_b64_decode(ctx, v, vl);
return (1);
}
struct tus_response {
unsigned magic;
#define VMOD_TUS_RESPONSE_MAGIC 0x1054e570
unsigned status;
const char *schemeauth; // from tussrv
const char *origin; // CORS
// if set, mtx is helt
struct tus_file_core *fcore;
};
void
tus_response(VRT_CTX, const struct VPFX(tus_server) *tussrv,
struct tus_response *resp);
VCL_BOOL
tus_done(VRT_CTX, const struct VPFX(tus_server) *tussrv,
const struct tus_response *resp, const char *url);
VCL_BOOL
tus_meta(VRT_CTX, const struct tus_response *resp, const char *k, VCL_BLOB *b);
extern const unsigned s_MULT;
extern const unsigned s_OPTIONS;
extern const unsigned s_HEAD;
// pseudo-opaque declaration
#ifndef VPFX
#define VPFX(x) tus_ ## x
#endif
struct VPFX(tus_server);
struct tus_exp;
struct vmod_blobdigest_digest;
// tus_servers.c
const char * tus_server_name(struct VPFX(tus_server) *);
struct tus_files *tus_server_files(struct VPFX(tus_server) *);
int tus_server_basefd(const struct VPFX(tus_server) *);
const char * tus_server_basedir(const struct VPFX(tus_server) *);
VCL_DURATION tus_server_expires(const struct VPFX(tus_server) *);
void tus_server_lock(struct VPFX(tus_server) *);
void tus_server_unlock(struct VPFX(tus_server) *);
VCL_BYTES tus_server_max(const struct VPFX(tus_server) *s);
const struct vmod_blobdigest_digest * tus_server_digest(
const struct VPFX(tus_server) *s);
struct tus_exp *tus_server_exp(const struct VPFX(tus_server) *);
#include "config.h"
#include <cache/cache.h>
#include "vcc_tus_if.h"
#include "tus_file.h"
#include "tus_server.h"
#include "tus_servers.h"
struct tus_servers tus_servers[1] = { VSPLAY_INITIALIZER(tus_servers) };
VSPLAY_GENERATE(tus_servers, VPFX(tus_server), entry, tus_server_cmp);
const char *
tus_server_name(struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->vcl_name);
}
struct tus_files *
tus_server_files(struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->files);
}
int
tus_server_basefd(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->basefd);
}
const char *
tus_server_basedir(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->basedir);
}
VCL_DURATION
tus_server_expires(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->expires);
}
void
tus_server_lock(struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
AZ(pthread_mutex_lock(&s->mtx));
}
void
tus_server_unlock(struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
AZ(pthread_mutex_unlock(&s->mtx));
}
VCL_BYTES
tus_server_max(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->max);
}
const struct vmod_blobdigest_digest *
tus_server_digest(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->digest);
}
struct tus_exp *
tus_server_exp(const struct VPFX(tus_server) *s)
{
CHECK_OBJ_NOTNULL(s, VMOD_TUS_SERVER_MAGIC);
return (s->exp);
}
#include <string.h>
#include <stdlib.h>
#include <vtree.h>
struct vmod_blobdigest_digest;
struct tus_exp;
/* ============================================================
* global server splay tree
*/
struct VPFX(tus_server) {
unsigned magic;
#define VMOD_TUS_SERVER_MAGIC 0x1055e47e
unsigned refcnt;
VSPLAY_ENTRY(VPFX(tus_server)) entry;
pthread_mutex_t mtx;
char *vcl_name;
char *basedir;
char *schemeauth;
VCL_BYTES max;
VCL_DURATION expires;
int basefd;
struct tus_files files[1];
const struct vmod_blobdigest_digest *digest;
struct tus_exp *exp;
};
static inline int
tus_server_cmp(const struct VPFX(tus_server) *a,
const struct VPFX(tus_server) *b) {
CHECK_OBJ_NOTNULL(a, VMOD_TUS_SERVER_MAGIC);
CHECK_OBJ_NOTNULL(b, VMOD_TUS_SERVER_MAGIC);
return (strcmp(a->vcl_name, b->vcl_name));
}
VSPLAY_HEAD(tus_servers, VPFX(tus_server));
struct tus_servers tus_servers[1];
VSPLAY_PROTOTYPE(tus_servers, VPFX(tus_server), entry, tus_server_cmp);
#include "config.h"
#include <sys/mman.h>
#include <cache/cache.h>
#include <cache/cache_obj.h>
#include <cache/cache_objhead.h>
#include <storage/storage.h>
#include "tus_file.h"
#include "tus_concat.h"
#include "tus_stv.h"
static void
tus_objfree(struct worker *wrk, struct objcore *oc);
static int
tus_objiterator(struct worker *wrk, struct objcore *oc,
void *priv, objiterate_f *func, int final);
static int
tus_objgetspace(struct worker *wrk, struct objcore *oc,
ssize_t *sz, uint8_t **ptr);
static void
tus_objextend(struct worker *wrk, struct objcore *oc, ssize_t l);
static const void *
tus_objgetattr(struct worker *wrk, struct objcore *oc,
enum obj_attr attr, ssize_t *len);
static void *
tus_objsetattr(struct worker *wrk, struct objcore *oc,
enum obj_attr attr, ssize_t len, const void *ptr);
const struct obj_methods obj_tus = {
.objfree = tus_objfree,
.objiterator = tus_objiterator,
.objgetspace = tus_objgetspace,
.objextend = tus_objextend,
.objgetattr = tus_objgetattr,
.objsetattr = tus_objsetattr
};
// ------------------------------------------------------------
static int
tus_allocobj(struct worker *wrk, const struct stevedore *stv,
struct objcore *oc, unsigned l)
{
(void) wrk;
(void) stv;
(void) oc;
(void) l;
INCOMPL();
return (0); // USELESS
}
const struct stevedore stv_tus = {
.magic = STEVEDORE_MAGIC,
.name = "tus",
.allocobj = tus_allocobj,
.methods = &obj_tus,
.ident = "tus",
.vclname = "tus"
};
/*
* ============================================================
*
*/
// instead of STV_NewObject()
//
// assign an existing tus file to an objcore
void
tus_body_assign(struct req *req, struct tus_concat *c)
{
struct objcore *oc;
CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CHECK_OBJ_NOTNULL(c, TUS_CONCAT_MAGIC);
if (req->body_oc != NULL)
AZ(HSH_DerefObjCore(req->wrk, &req->body_oc, 0));
AZ(req->body_oc);
req->body_oc = oc = HSH_Private(req->wrk);
AN(oc);
AZ(oc->stobj->stevedore);
AZ(oc->stobj->priv);
oc->oa_present = 0;
oc->stobj->stevedore = &stv_tus;
oc->stobj->priv = c;
//oc->stobj->priv2 = 0;
req->req_body_status = BS_CACHED;
HSH_DerefBoc(req->wrk, oc);
}
static void
tus_objfree(struct worker *wrk, struct objcore *oc)
{
(void) wrk;
// not actually freeing it, the tus object gets expired / refcounted
oc->stobj->stevedore = NULL;
oc->stobj->priv = 0;
}
static int
tus_objiterator(struct worker *wrk, struct objcore *oc,
void *priv, objiterate_f *func, int final)
{
// sensible upper limit taking into account tcp windows
const unsigned max_write = 16 * 1024 * 1024;
struct tus_concat *c;
struct tus_file_core *fcore;
unsigned i, off, len;
void *p;
int r = -1;
(void) wrk;
CHECK_OBJ_NOTNULL(oc, OBJCORE_MAGIC);
assert(oc->stobj->stevedore == &stv_tus);
CAST_OBJ_NOTNULL(c, oc->stobj->priv, TUS_CONCAT_MAGIC);
for (i = 0; i < c->n; i++) {
fcore = c->cores[i];
CHECK_OBJ_NOTNULL(fcore, VMOD_TUS_FILE_CORE_MAGIC);
if (fcore->len == 0)
continue;
AN(fcore->ptr);
p = fcore->ptr;
len = fcore->len;
AZ(posix_madvise(p, len, POSIX_MADV_SEQUENTIAL));
AZ(posix_madvise(p, len, POSIX_MADV_WILLNEED));
off = 0;
do {
len = fcore->len - off;
if (len > max_write)
len = max_write;
p = (char *)fcore->ptr + off;
r = func(priv, OBJ_ITER_FINAL | OBJ_ITER_FLUSH, p, len);
if (r < 0)
return (r);
AZ(posix_madvise(p, len, POSIX_MADV_DONTNEED));
off += len;
} while (off < fcore->len);
}
return (r);
}
static int
tus_objgetspace(struct worker *wrk, struct objcore *oc,
ssize_t *sz, uint8_t **ptr)
{
(void) wrk;
(void) oc;
(void) sz;
(void) ptr;
INCOMPL();
return (0); // USELESS
}
static void
tus_objextend(struct worker *wrk, struct objcore *oc, ssize_t l)
{
(void) wrk;
(void) oc;
(void) l;
INCOMPL();
}
static const void *
tus_objgetattr(struct worker *wrk, struct objcore *oc,
enum obj_attr attr, ssize_t *len)
{
(void) wrk;
(void) oc;
(void) attr;
(void) len;
INCOMPL();
return (NULL); // USELESS
}
static void *
tus_objsetattr(struct worker *wrk, struct objcore *oc,
enum obj_attr attr, ssize_t len, const void *ptr)
{
(void) wrk;
(void) oc;
(void) attr;
(void) len;
(void) ptr;
INCOMPL();
return (NULL); // USELESS
}
// helper
struct tus_concat *
tus_body_single(struct req *req, struct tus_file_core *fcore)
{
struct tus_concat *c;
c = WS_Alloc(req->ws, sizeof *c + sizeof fcore);
if (c == NULL)
return (NULL);
INIT_OBJ(c, TUS_CONCAT_MAGIC);
c->n = 1;
c->cores[0] = fcore;
return (c);
}
struct tus_concat;
void tus_body_assign(struct req *req, struct tus_concat *concat);
struct tus_concat *tus_body_single(struct req *req, struct tus_file_core *fcore);
#include "config.h"
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "vdef.h"
#include "vrt.h"
int
main(int argc, char **argv) {
void *dlhdl;
char buf[256];
const struct vmod_data *d;
const char *name = argv[1];
const char *file = argv[2];
dlhdl = dlopen(file, RTLD_LAZY | RTLD_LOCAL);
if (dlhdl == NULL) {
fprintf(stderr, "dlopen: %s\n", dlerror());
return (1);
}
bprintf(buf, "Vmod_%s_Data", name);
d = dlsym(dlhdl, buf);
if (d == NULL) {
fprintf(stderr, "dlsym: %s\n", dlerror());
return (1);
}
printf(d->proto);
printf("\nstatic const struct vmod_data import_%s = {\n", buf);
printf("\t.name =\t\t\"%s\",\n", d->name);
printf("\t.file_id =\t\"%s\",\n", d->file_id);
printf("\t.abi =\t\t\"%s\",\n", d->abi);
printf("};\n");
return (0);
}
#include "config.h" #include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <stdlib.h>
#include <cache/cache.h> #include <cache/cache.h>
#include <vcl.h>
#include <vrt_obj.h>
#include "vcc_tus_if.h" #include "vcc_tus_if.h"
VCL_STRING #include "tus_file.h"
vmod_hello(VRT_CTX) #include "tus_servers.h"
#include "tus_response.h"
#include "tus_request.h"
#include "tus_blob.h"
// tus_file_exp.c
struct tus_exp *tus_file_exp_new(void);
void tus_file_exp_destroy(struct tus_exp **ep);
unsigned refcnt = 0;
/* ============================================================
* vmod init/fini
*/
static int
tus_init(VRT_CTX, struct vmod_priv *priv)
{
(void) priv;
if (refcnt++ > 0)
return (0);
tus_file_init();
return (tus_chksum_init(ctx));
}
static int
tus_fini(VRT_CTX, struct vmod_priv *priv)
{
(void) priv;
AN(refcnt--);
if (refcnt > 0)
return (0);
return (tus_chksum_fini(ctx));
}
int
tus_event(VRT_CTX, struct vmod_priv *priv, enum vcl_event_e e)
{
switch (e) {
case VCL_EVENT_LOAD: return (tus_init(ctx, priv));
case VCL_EVENT_DISCARD: return (tus_fini(ctx, priv));
case VCL_EVENT_WARM:
case VCL_EVENT_COLD:
return (0);
default: INCOMPL();
}
}
/* ============================================================
* constructor/destructor
*/
/*
struct VARGS(server__init) {
char valid_basedir;
VCL_STRING basedir;
};
*/
#ifndef O_DIRECTORY
#define O_DIRECTORY 0
#endif
static int
tus_dir_chk(const char *dir)
{
struct stat st;
int i;
i = stat(dir, &st);
if (i)
return (i);
if (! S_ISDIR(st.st_mode)) {
errno = ENOTDIR;
return (-1);
}
i = access(dir, R_OK | W_OK);
if (i)
return (i);
return (0);
}
int
tus_basefd(const char *dir)
{
if (tus_dir_chk(dir)) {
if (mkdir(dir, 0700))
return (-1);
if (tus_dir_chk(dir))
return (-1);
}
return (open(dir, O_DIRECTORY));
}
static int
tus_schemeauth_valid(const char *schemeauth, const char **p)
{
*p = schemeauth;
if (*p == NULL || strncmp(*p, "http", 4) != 0)
return (0);
*p += 4;
if (**p == 's')
(*p)++;
if (strncmp(*p, "://", 3) != 0)
return (0);
*p += 3;
if (**p == '\0')
return (0);
if ((*p = strchr(*p, '/')))
return (0);
return (1);
}
static struct VPFX(tus_server) *
tus_server_new(VRT_CTX, const char *vcl_name, struct VARGS(server__init) *args)
{ {
struct VPFX(tus_server) *tussrv;
const char *basedir, *p;
char buf[PATH_MAX];
int basefd;
if (! tus_schemeauth_valid(args->schemeauth, &p)) {
VRT_fail(ctx, "tus server %s: invalid schemeauth %s "
"at ...>%.4s<...", vcl_name, args->schemeauth, p);
return (NULL);
}
basedir = args->valid_basedir ? args->basedir : vcl_name;
basefd = tus_basefd(basedir);
if (basefd < 0) {
VRT_fail(ctx,
"tus server %s: error %d (%s) opening basedir",
vcl_name, errno, strerror(errno));
return (NULL);
}
basedir = realpath(basedir, buf);
if (basedir == NULL) {
VRT_fail(ctx,
"tus server %s: error %d (%s) resolving basedir",
vcl_name, errno, strerror(errno));
return (NULL);
}
ALLOC_OBJ(tussrv, VMOD_TUS_SERVER_MAGIC);
AN(tussrv);
AZ(pthread_mutex_init(&tussrv->mtx, NULL));
REPLACE(tussrv->vcl_name, vcl_name);
REPLACE(tussrv->schemeauth, args->schemeauth);
REPLACE(tussrv->basedir, basedir);
tussrv->basefd = basefd;
tussrv->refcnt = 1;
tussrv->exp = tus_file_exp_new();
AZ(VSPLAY_INSERT(tus_servers, tus_servers, tussrv));
return (tussrv);
}
static struct VPFX(tus_server) *
tus_server_ref(VRT_CTX, struct VPFX(tus_server) *tussrv,
struct VARGS(server__init) *args)
{
const char *basedir = tussrv->basedir;
AN(basedir);
if (! args->valid_basedir || strcmp(basedir, args->basedir) == 0) {
tussrv->refcnt++;
return (tussrv);
}
VRT_fail(ctx,
"tus server %s: attempt to change basedir from %s to %s",
tussrv->vcl_name, basedir, args->basedir);
return (NULL);
}
VCL_VOID
tus_server__init(VRT_CTX, struct VPFX(tus_server) **tussrvp,
const char *vcl_name, struct VARGS(server__init) *args)
{
const struct vmod_blobdigest_digest *d = NULL;
struct VPFX(tus_server) *tussrv, needle[1];
AN(tussrvp);
AZ(*tussrvp);
if (args->valid_name_hash) {
d = tus_hash(args->name_hash, 0);
if (d == NULL) {
VRT_fail(ctx, "new %s: "
"name_hash %s not supported "
"(see \"Hashes\" in documentation)",
vcl_name, args->name_hash);
return;
}
}
INIT_OBJ(needle, VMOD_TUS_SERVER_MAGIC);
needle->vcl_name = TRUST_ME(vcl_name);
tussrv = VSPLAY_FIND(tus_servers, tus_servers, needle);
if (tussrv == NULL)
tussrv = tus_server_new(ctx, vcl_name, args);
else
tussrv = tus_server_ref(ctx, tussrv, args);
if (tussrv == NULL)
return;
tussrv->max = args->max;
tussrv->expires = args->expires;
tussrv->digest = d;
AZ(pthread_mutex_lock(&tussrv->mtx));
tus_file_load(tussrv);
AZ(pthread_mutex_unlock(&tussrv->mtx));
*tussrvp = tussrv;
return;
}
VCL_VOID
tus_server__fini(struct VPFX(tus_server) **tussrvp)
{
struct VPFX(tus_server) *tussrv, *remove;
TAKE_OBJ_NOTNULL(tussrv, tussrvp, VMOD_TUS_SERVER_MAGIC);
assert(tussrv->refcnt >= 1);
if (--tussrv->refcnt == 0) {
tus_file_shutdown(tussrv);
tus_file_exp_destroy(&tussrv->exp);
AZ(tussrv->exp);
remove = VSPLAY_REMOVE(tus_servers, tus_servers, tussrv);
assert (remove == tussrv);
AZ(pthread_mutex_destroy(&tussrv->mtx));
REPLACE(tussrv->vcl_name, NULL);
REPLACE(tussrv->basedir, NULL);
AZ(close(tussrv->basefd));
FREE_OBJ(tussrv);
}
return;
}
/* ============================================================
* response to carry from recv to deliver
*/
static void
tus_task_free(void *ptr)
{
struct tus_response *r;
CAST_OBJ_NOTNULL(r, ptr, VMOD_TUS_RESPONSE_MAGIC);
if (r->fcore == NULL)
return;
AZ(pthread_mutex_unlock(&r->fcore->mtx));
r->fcore = NULL;
}
struct tus_response *
tus_task_new(VRT_CTX, struct VPFX(tus_server) *tussrv)
{
struct tus_response *r;
struct vmod_priv *task;
task = VRT_priv_task(ctx, tussrv);
if (task == NULL) {
VRT_fail(ctx, "no priv_task");
return (NULL);
}
if (task->priv) {
VRT_fail(ctx, "A tus request can only be started once");
return (NULL);
}
r = WS_Alloc(ctx->ws, sizeof *r);
if (r == NULL) {
VRT_fail(ctx, "WS_Alloc failed");
return (NULL);
}
INIT_OBJ(r, VMOD_TUS_RESPONSE_MAGIC);
task->priv = r;
task->free = tus_task_free;
return (r);
}
struct tus_response *
tus_task_use(VRT_CTX, struct VPFX(tus_server) *tussrv)
{
struct tus_response *r;
struct vmod_priv *task;
task = VRT_priv_task(ctx, tussrv);
if (task == NULL) {
VRT_fail(ctx, "no priv_task");
return (NULL);
}
if (task->priv == NULL) {
VRT_fail(ctx, "No tus request present");
return (NULL);
}
CAST_OBJ_NOTNULL(r, task->priv, VMOD_TUS_RESPONSE_MAGIC);
return (r);
}
/* ============================================================
* tus ops
*/
/* ============================================================
* recv / deliver
*/
/*
struct VARGS(server_recv) {
char valid_url;
VCL_STRING url;
};
*/
VCL_BOOL
tus_server_recv(VRT_CTX, struct VPFX(tus_server) *tussrv,
struct VARGS(server_recv) *args)
{
struct tus_response *r;
VCL_STRING url;
VCL_STRING id = NULL;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC); CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
return ("vmod-tus"); CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if (ctx->method != VCL_MET_RECV) {
VRT_fail(ctx, "%s.recv() must only be called from vcl_recv{}",
tussrv->vcl_name);
return (0);
}
url = args->valid_url ? args->url : VRT_r_req_url(ctx);
if (*url != '/') {
VRT_fail(ctx, "%s.recv() invalid url", tussrv->vcl_name);
return (0);
}
if (args->valid_id && args->id != NULL && *args->id != '\0')
id = args->id;
r = tus_task_new(ctx, tussrv);
if (r == NULL)
return (0);
return (tus_request(ctx, tussrv, r, url, id));
}
VCL_BOOL
tus_server_deliver(VRT_CTX, struct VPFX(tus_server) *tussrv)
{
struct tus_response *r;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if (ctx->method != VCL_MET_DELIVER) {
VRT_fail(ctx, "%s.deliver() must only be called "
"from vcl_deliver{}", tussrv->vcl_name);
return (0);
}
r = tus_task_use(ctx, tussrv);
if (r == NULL)
return (0);
tus_response(ctx, tussrv, r);
return (0);
}
VCL_BOOL
tus_server_synth(VRT_CTX, struct VPFX(tus_server) *tussrv)
{
struct tus_response *r;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if (ctx->method != VCL_MET_SYNTH) {
VRT_fail(ctx, "%s.synth() must only be called "
"from vcl_synth{}", tussrv->vcl_name);
return (0);
}
r = tus_task_use(ctx, tussrv);
if (r == NULL)
return (0);
tus_response(ctx, tussrv, r);
return (0);
}
VCL_BOOL tus_server_done(VRT_CTX, struct VPFX(tus_server) *tussrv,
struct VARGS(server_done)*args)
{
struct tus_response *r;
const char * url = NULL;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if ((ctx->method & VCL_MET_TASK_C) == 0) {
VRT_fail(ctx, "%s.done() must only be called "
"from client VCL subroutines", tussrv->vcl_name);
return (0);
}
r = tus_task_use(ctx, tussrv);
if (r == NULL)
return (0);
if (args->valid_location)
url = args->location;
tus_done(ctx, tussrv, r, url);
return (0);
}
VCL_BOOL
tus_server_has_metadata(VRT_CTX, struct VPFX(tus_server) *tussrv,
VCL_STRING key)
{
struct tus_response *r;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if ((ctx->method & VCL_MET_TASK_C) == 0) {
VRT_fail(ctx, "%s.has_metadata() must only be called "
"from client VCL subroutines", tussrv->vcl_name);
return (0);
}
r = tus_task_use(ctx, tussrv);
if (r == NULL)
return (0);
return (tus_meta(ctx, r, key, NULL));
}
VCL_BLOB
tus_server_metadata(VRT_CTX, struct VPFX(tus_server) *tussrv,
VCL_STRING key)
{
struct tus_response *r;
VCL_BLOB b;
CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(tussrv, VMOD_TUS_SERVER_MAGIC);
if ((ctx->method & VCL_MET_TASK_C) == 0) {
VRT_fail(ctx, "%s.metadata() must only be called "
"from client VCL subroutines", tussrv->vcl_name);
return (NULL);
}
r = tus_task_use(ctx, tussrv);
if (r == NULL)
return (NULL);
if (tus_meta(ctx, r, key, &b) == 0)
return (NULL);
return (b);
} }
$Module tus 3 "Varnish tus Module" $Module tus 3 "Varnish tus Module"
$Event event
$Prefix tus
DESCRIPTION DESCRIPTION
=========== ===========
This VCC file was generated by VCDK, it is used to for both the VMOD .. _tus: https://tus.io/protocols/resumable-upload.html
interface and its manual using reStructuredText.
XXX: document vmod-tus This vmod implements a `tus`_ proxy which collects uploads on the
varnish server to send them to a backend in one go for storage. It
does not implement any permanent storage itself.
Example Besides the basic resumable uploads specified as the `tus`_ core
:: protocol, all currently defined extensions are supported, in
particular concatenation uploads.
This vmod implements methods to be called from VCL and requires a
common call pattern for normal operation. When used this way, this
vmod handles all client interaction via synthetic responses until an
upload is complete. For the client request concluding the upload, it
modifies the request into a single ``PUT`` and provides a single
request body to be sent to a backend.
By this VCL integration, http behaviour remains highly customizable.
CORS
----
The `xserver.synth()`_ and `xserver.deliver()`_ methods will set the
right CORS headers allowing all Origins, that is,
``Access-Control-Allow-Origin`` set to ``Origin``. As any other
header, the CORS headers can be changes after the call to
`xserver.synth()`_, and should be to restrict Origin access.
Hashes
------
.. _vmod_blobdigest: https://code.uplex.de/uplex-varnish/libvmod-blobdigest
This vmod supports checking and generating content hashes if
`vmod_blob` (bundled with varnish-cache) and `vmod_blobdigest`_ are
* installed at build time
* installed in exactly the same version at run time
* and imported to the VCL before ``tus``, like so::
import blob;
import blobdigest;
import tus; import tus;
sub vcl_deliver { Given these preconditions are true, thus vmod supports the following
set resp.http.Hello = tus.hello(); hashes as ``Tus-Checksum-Algorithm`` and as the ``name_hash`` argument
to `tus.server()`_:
* crc32
* icrc32
* md5
* rs
* sha1
* sha224
* sha256
* sha384
* sha3_224
* sha3_256
* sha3_384
* sha3_512
* sha512
$Object server(STRING schemeauth, BYTES max=1073741824,
DURATION expires=86400, [STRING basedir],
[STRING name_hash])
Declare a tus server at the given ``schemeauth``, which is the
protocol and servername, like ``https://tus.io``.
Tus servers are shared across VCLs. Each server has its own namespace.
The ``max`` argument (defaulting to 1GB) specifies the maximum upload
size supported by the server. The actual maximum upload size supported
as reportde in the ``Tus-Max-Size`` header will be that value capped
by the available disk space in the ``basedir``. Notice that as the
available space might vary inbetween requests, so will the reported
space, and uploads might still fail. Thus the ``max`` argument is
recommended to be chosen sufficiently small compared to the available
disk space and disk space be monitored.
The ``expires`` argument (defaulting to 1 day) specifies the duration
until unfinished uploads expire and are removed. The expiry gets
extended by that duration with every operation on a file.
.. XXX warm event? per VCL object?
The ``schemeauth``, ``max`` and ``expires`` values for an already defined
server *can* be changed. The value used in the vcl *loaded* last
applies.
The optional ``basedir`` argument can be used to define a directory
under which this server's objects are stored. This directory or its
parent directory should exist.
Sharing basedirs between servers is not supported. An attempting to
change the ``basedir`` for a server already defined through a
different VCL is an error.
The optional ``name_hash`` argument can be used to instruct the tus
server to use the hex encoding of a hash over the content the upload
as the last part of the url when constructing the backend ``PUT``. For
``Upload-Concat: final`` uploads, this name is also used as the
``Location`` returned to the client.
$Method BOOL .recv([STRING url], [STRING id])
Process a tus request.
If the `url` argument is not given, it is taken from ``req.url``.
The optional `id` argument allows to override the id assigned by the
tus server for a create (HTTP ``POST``). Notice that, while id is
normally guaranteed to be unique for the currently active uploads, a
``POST`` may fail for a duplicate `id` argument. `id` has no relevance
for any request but ``POST``.
If the return value is ``true``, the request body has been replaced
with an object ready to be sent to the backend, so a backend call
should be made to send data to storage.
If the reuturn value is ``false``, a synthetic reponse has to be
generated, so ``return(synth(code))`` should be returned such that
`xserver.synth()`_ will be called for ``code``.
Thus, the common call pattern is::
sub vcl_recv {
if (mytus.recv()) {
return (pass);
} else {
return (synth(4200));
}
}
Must be called from ``vcl_recv {}``
$Method BOOL .deliver()
Generate a response to a tus request for which content was
stored. Must be called from ``vcl_deliver {}`` after `xserver.recv()`_
was called from ``vcl_recv {}``.
$Method BOOL .synth()
Generate a synthetic response to a tus request. Must be called from
``vcl_synth {}`` after `xserver.recv()`_ was called from ``vcl_recv {}``.
For the example above, the code would be::
sub vcl_synth {
if (resp.status == 4200) {
mytus.synth();
return (deliver);
} }
}
$Method BOOL .done([STRING location])
Mark the upload as done: This frees all storage except for information
on the upload URL itself.
If the optional *location* string is provided, a 301 redirect to
*location* will be generated for any access attempt. Otherwise, a 410
response will be generated.
May only be called from client methods. For anything but final concat
or single (non-concat) uploads, this operation is a noop.
$Method BOOL .has_metadata(STRING key)
Return true if *key* is present in the metadata.
Only available if vmod_blob is available (see `Hashes` _) and on the
client side after `xserver.recv()`_ was called from ``vcl_recv {}``.
XXX: define vmod-tus interface $Method BLOB .metadata(STRING key)
$Function STRING hello() Extract *key* from metadata and return the corresponding value decoded.
Description Only available if vmod_blob is available (see `Hashes` _) and on the
Hello world for vmod-tus client side after `xserver.recv()`_ was called from ``vcl_recv {}``.
SEE ALSO SEE ALSO
========vcl\(7),varnishd\(1) ========vcl\(7),varnishd\(1)
varnishtest "test vmod-tus set done redirect"
server s1 {
rxreq
txresp
expect req.method == PUT
expect req.bodylen == 100
} -start
varnish v1 -vcl+backend {
import blob;
import blobdigest;
import tus;
sub vcl_init {
new tmp = tus.server("http://localhost",
basedir="/tmp/tus", max = 3145B);
}
sub vcl_backend_fetch {
if (bereq.url ~ "^/id") {
set bereq.backend = s1;
} else {
return (abandon);
}
}
sub vcl_recv {
if (tmp.recv(id=req.http.id)) {
return(pass);
} else {
return(synth(4200));
}
}
sub vcl_synth {
if (resp.status == 4200) {
tmp.synth();
return (deliver);
}
}
sub vcl_deliver {
tmp.deliver();
tmp.done(req.url);
}
} -start
# dynamic file name complete post
client c1 {
txreq -method "DELETE" -url "/id" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-hdr "Id: id" \
-bodylen 100
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/id"
txreq -url "/id" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 301
expect resp.http.Location == "http://localhost/id"
} -run
varnishtest "test expiring files"
varnish v1 -vcl {
import tus;
backend dummy None;
sub vcl_init {
new tmp = tus.server("https://my.origin",
basedir="/tmp/tusexp", expires=1s);
}
sub vcl_recv {
if (tmp.recv(id=req.http.id)) {
return(fail);
} else {
return(synth(4200));
}
}
sub vcl_synth {
if (resp.status == 4200) {
tmp.synth();
return (deliver);
}
}
sub vcl_deliver {
tmp.deliver();
}
} -start
client c1 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-hdr "Id: c1" \
-hdr "Content-Length: 0" \
-nolen
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "https://my.origin/c1"
txreq -method HEAD -url "/c1" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
delay 2
txreq -method HEAD -url "/c1" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 404
} -start
client c2 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-hdr "Id: c2" \
-hdr "Content-Length: 0" \
-nolen
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "https://my.origin/c2"
txreq -method HEAD -url "/c2" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
delay 2
txreq -method HEAD -url "/c2" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 404
} -start
client c3 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-hdr "Id: c3" \
-hdr "Content-Length: 0" \
-nolen
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "https://my.origin/c3"
txreq -method HEAD -url "/c3" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
delay 2
txreq -method HEAD -url "/c3" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 404
} -start
client c4 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-hdr "Id: c4" \
-hdr "Content-Length: 0" \
-nolen
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "https://my.origin/c4"
txreq -method HEAD -url "/c4" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation"
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
delay 2
txreq -method HEAD -url "/c4" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 404
} -start
client c1 -wait
client c2 -wait
client c3 -wait
client c4 -wait
varnishtest "test vmod-tus with name_hash"
barrier b1 cond 2
barrier b2 cond 2
server s1 {
rxreq
txresp
expect req.method == PUT
expect req.url == /c0ac9c73c39c8e396e9c18b2b5279d2df090d93b
expect req.bodylen == 100
} -start
server s2 {
rxreq
txresp
expect req.method == PUT
expect req.url == /86de97e0b0d86089c3428e2654ba7c0292849281
expect req.bodylen == 100
} -start
server s3 {
rxreq
txresp
expect req.method == PUT
expect req.url == /d641a99eb4143c2553360d1ef4a3f002e1079cb8
expect req.bodylen == 200
} -start
server s4 {
rxreq
txresp
expect req.method == PUT
expect req.url == /da39a3ee5e6b4b0d3255bfef95601890afd80709
expect req.bodylen == 0
} -start
varnish v1 -vcl+backend {
import blob;
import blobdigest;
import tus;
sub vcl_init {
new tmp = tus.server("http://localhost",
basedir="/tmp/tus_name_hash", max = 3145B, name_hash = "sha1");
}
sub vcl_backend_fetch {
if (bereq.url ~ "^/c0ac9c73c39c8e396e9c18b2b5279d2df090d93b") {
set bereq.backend = s1;
} else if (bereq.url == "/86de97e0b0d86089c3428e2654ba7c0292849281") {
set bereq.backend = s2;
} else if (bereq.url == "/d641a99eb4143c2553360d1ef4a3f002e1079cb8") {
set bereq.backend = s3;
} else if (bereq.url == "/da39a3ee5e6b4b0d3255bfef95601890afd80709") {
set bereq.backend = s4;
} else {
return (abandon);
}
}
sub vcl_recv {
if (tmp.recv(id=req.http.id)) {
return(pass);
} else {
return(synth(4200));
}
}
sub vcl_synth {
if (resp.status == 4200) {
tmp.synth();
return (deliver);
}
}
sub vcl_deliver {
tmp.deliver();
}
} -start
# dynamic file name complete post
client c1 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-bodylen 100
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location ~ "^http://localhost/tus"
} -start
# POST/PATCH test with vtc-provided ID
client c2 {
# clean out
txreq -url "/vtc" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
# partial Upload with create
txreq -method POST \
-hdr "Upload-Metadata: filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential" \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Id: vtc" \
-hdr "Upload-Checksum: sha256 YPa+R2sYtYBoQbCv/Oy9B/MN3AJR4z5GQBHBlt+0Cu8=" \
-bodylen 40
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
## XXX only for HEAD?
expect resp.http.Upload-Metadata == "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential"
expect resp.http.Location == "http://localhost/vtc"
# finish upload
txreq -method PATCH -url "/vtc" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 40" \
-hdr "Upload-Checksum: sha256 6+WLYHJamF+3jynYkIASJo7w5z10Lt0e862/sEg/IXo=" \
-bodylen 60
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
## XXX only for HEAD?
expect resp.http.Upload-Metadata == "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential"
} -start
# part1
client c3 {
# clean out
txreq -url "/part1" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Id: part1" \
-hdr "Upload-Checksum: sha256 YPa+R2sYtYBoQbCv/Oy9B/MN3AJR4z5GQBHBlt+0Cu8=" \
-hdr "Upload-Concat: partial" \
-bodylen 40
rxresp
barrier b1 sync
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/part1"
txreq -method HEAD -url "/part1" \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
# finish upload
txreq -method PATCH -url "/part1" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 40" \
-hdr "Upload-Checksum: sha256 6+WLYHJamF+3jynYkIASJo7w5z10Lt0e862/sEg/IXo=" \
-bodylen 60
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
# part2
client c4 {
# clean out
txreq -url "/part2" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id: part2" \
-hdr "Upload-Defer-Length: 1" \
-hdr "Upload-Concat: partial"
rxresp
barrier b2 sync
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Defer-Length == 1
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/part2"
txreq -method HEAD -url "/part2" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Defer-Length == 1
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Upload-Concat == partial
# upload
txreq -method PATCH -url "/part2" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 0" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-bodylen 100
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
# concat
client c5 {
# clean out
txreq -url "/d641a99eb4143c2553360d1ef4a3f002e1079cb8" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
barrier b1 sync
barrier b2 sync
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Upload-Concat: final; /part1 /part2"
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 200
expect resp.http.Upload-Length == 200
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/d641a99eb4143c2553360d1ef4a3f002e1079cb8"
txreq -url /d641a99eb4143c2553360d1ef4a3f002e1079cb8 \
-method HEAD \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 200
expect resp.http.Upload-Length == 200
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Upload-Concat == "final;http://localhost/part1 http://localhost/part2"
# fail patch on concat
txreq -url /d641a99eb4143c2553360d1ef4a3f002e1079cb8 \
-method PATCH \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 403
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# bad concat
client c6 {
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id:badconcat" \
-hdr "Upload-Concat: final;/foo"
rxresp
expect resp.status == 400
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id:badconcat2" \
-hdr "Upload-Concat: final;"
rxresp
expect resp.status == 400
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# exceeding max size
client c7 {
txreq -method POST \
-hdr "Upload-Length: 3146" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-bodylen 3146
rxresp
expect resp.status == 413
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
## XXX correct?
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# bad checksum
client c8 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: md5 ABCD" \
-bodylen 100
rxresp
expect resp.status == 460
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
client c9 {
# clean out
txreq -url "/empty-single" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Upload-Length: 0" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id: empty-single" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Content-Length: 0"
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location ~ "^http://localhost/empty-single"
} -start
client c1 -wait
client c2 -wait
client c3 -wait
client c4 -wait
client c5 -wait
client c6 -wait
client c7 -wait
client c8 -wait
client c9 -wait
varnishtest "test vmod-tus with ids"
barrier b1 cond 2
barrier b2 cond 2
server s1 {
rxreq
txresp
expect req.method == PUT
expect req.bodylen == 100
} -start
server s2 {
rxreq
txresp
expect req.method == PUT
expect req.url == /vtc
expect req.bodylen == 100
} -start
server s3 {
rxreq
txresp
expect req.method == PUT
expect req.url == /concat
expect req.bodylen == 200
} -start
server s4 {
rxreq
txresp
expect req.method == PUT
expect req.url == /empty-single
expect req.bodylen == 0
} -start
varnish v1 -vcl+backend {
import blob;
import blobdigest;
import tus;
sub vcl_init {
new test = tus.server("https://my.origin");
new tmp = tus.server("http://localhost",
basedir="/tmp/tus", max = 3145B);
}
sub vcl_backend_fetch {
if (bereq.url ~ "^/tus") {
set bereq.backend = s1;
} else if (bereq.url == "/vtc") {
set bereq.backend = s2;
} else if (bereq.url == "/concat") {
set bereq.backend = s3;
} else if (bereq.url == "/empty-single") {
set bereq.backend = s4;
} else {
return (abandon);
}
}
sub vcl_recv {
if (tmp.recv(id=req.http.id)) {
return(pass);
} else {
return(synth(4200));
}
}
sub meta {
set resp.http.has-filename = tmp.has_metadata("filename");
set resp.http.has-is_confidential =
tmp.has_metadata("is_confidential");
set resp.http.has-filenamee = tmp.has_metadata("filenamee");
set resp.http.has-filenam = tmp.has_metadata("filenam");
set resp.http.filename = blob.encode(IDENTITY, blob=tmp.metadata("filename"));
set resp.http.is_confidential = blob.encode(IDENTITY, blob=tmp.metadata("is_confidential"));
set resp.http.filenam = blob.encode(IDENTITY, blob=tmp.metadata("filenam"));
}
sub vcl_synth {
if (resp.status == 4200) {
call meta;
tmp.synth();
return (deliver);
}
}
sub vcl_deliver {
call meta;
tmp.deliver();
}
} -start
varnish v1 -errvcl "attempt to change basedir" {
import tus;
backend dummy None;
sub vcl_init {
new test = tus.server("http://localhost", basedir="different");
}
}
varnish v1 -errvcl "Argument 'schemeauth' missing" {
import tus;
backend dummy None;
sub vcl_init {
new test = tus.server(basedir="different");
}
}
varnish v1 -errvcl "opening basedir" {
import tus;
backend dummy None;
sub vcl_init {
new no_way = tus.server(schemeauth="http://localhost",
basedir="/dev/null/foo");
}
}
# dynamic file name complete post
client c1 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-bodylen 100
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location ~ "^http://localhost/tus"
} -start
# POST/PATCH test with vtc-provided ID
client c2 {
# clean out
txreq -url "/vtc" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
# partial Upload with create
txreq -method POST \
-hdr "Upload-Metadata: filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential" \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Id: vtc" \
-hdr "Upload-Checksum: sha256 YPa+R2sYtYBoQbCv/Oy9B/MN3AJR4z5GQBHBlt+0Cu8=" \
-bodylen 40
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
## XXX only for HEAD?
expect resp.http.Upload-Metadata == "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential"
expect resp.http.Location == "http://localhost/vtc"
expect resp.http.has-filename == true
expect resp.http.has-is_confidential == true
expect resp.http.has-filenamee == false
expect resp.http.has-filenam == false
expect resp.http.filename == "world_domination_plan.pdf"
expect resp.http.is_confidential == ""
expect resp.http.filenam == ""
# finish upload
txreq -method PATCH -url "/vtc" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 40" \
-hdr "Upload-Checksum: sha256 6+WLYHJamF+3jynYkIASJo7w5z10Lt0e862/sEg/IXo=" \
-bodylen 60
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
## XXX only for HEAD?
expect resp.http.Upload-Metadata == "filename d29ybGRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential"
expect resp.http.has-filename == true
expect resp.http.has-is_confidential == true
expect resp.http.has-filenamee == false
expect resp.http.has-filenam == false
expect resp.http.filename == "world_domination_plan.pdf"
expect resp.http.is_confidential == ""
expect resp.http.filenam == ""
} -start
# part1
client c3 {
# clean out
txreq -url "/part1" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Id: part1" \
-hdr "Upload-Checksum: sha256 YPa+R2sYtYBoQbCv/Oy9B/MN3AJR4z5GQBHBlt+0Cu8=" \
-hdr "Upload-Concat: partial" \
-bodylen 40
rxresp
barrier b1 sync
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/part1"
txreq -method HEAD -url "/part1" \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 40
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
# finish upload
txreq -method PATCH -url "/part1" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 40" \
-hdr "Upload-Checksum: sha256 6+WLYHJamF+3jynYkIASJo7w5z10Lt0e862/sEg/IXo=" \
-bodylen 60
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
# part2
client c4 {
# clean out
txreq -url "/part2" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id: part2" \
-hdr "Upload-Defer-Length: 1" \
-hdr "Upload-Concat: partial"
rxresp
barrier b2 sync
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Defer-Length == 1
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/part2"
txreq -method HEAD -url "/part2" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Defer-Length == 1
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Upload-Concat == partial
# upload
txreq -method PATCH -url "/part2" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Length: 100" \
-hdr "Upload-Offset: 0" \
-hdr "Upload-Checksum: sha256 H98ak0Maj3gjAqfjx2KIqrwAil7CHeKsnJwyzLAXEu8=" \
-bodylen 100
rxresp
expect resp.status == 204
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 100
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
# concat
client c5 {
# clean out
txreq -url "/concat" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
barrier b1 sync
barrier b2 sync
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id: concat" \
-hdr "Upload-Concat: final; /part1 /part2"
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 200
expect resp.http.Upload-Length == 200
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location == "http://localhost/concat"
txreq -url /concat \
-method HEAD \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 200
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 200
expect resp.http.Upload-Length == 200
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Upload-Concat == "final;http://localhost/part1 http://localhost/part2"
# fail patch on concat
txreq -url /concat \
-method PATCH \
-hdr "Tus-Resumable: 1.0.0"
rxresp
expect resp.status == 403
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# bad concat
client c6 {
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id:badconcat" \
-hdr "Upload-Concat: final;/foo"
rxresp
expect resp.status == 400
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
txreq -url / \
-method POST \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id:badconcat2" \
-hdr "Upload-Concat: final;"
rxresp
expect resp.status == 400
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# exceeding max size
client c7 {
txreq -method POST \
-hdr "Upload-Length: 3146" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-bodylen 3146
rxresp
expect resp.status == 413
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
## XXX correct?
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == <undef>
expect resp.http.Upload-Length == <undef>
expect resp.http.Upload-Expires == <undef>
expect resp.http.Location == <undef>
} -start
# bad checksum
client c8 {
txreq -method POST \
-hdr "Upload-Length: 100" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Upload-Checksum: md5 ABCD" \
-bodylen 100
rxresp
expect resp.status == 460
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 100
expect resp.http.Upload-Expires ~ "GMT$"
} -start
client c9 {
# clean out
txreq -url "/empty-single" \
-method DELETE \
-hdr "Content-Length: 0" \
-hdr "Tus-Resumable: 1.0.0"
rxresp
txreq -method POST \
-hdr "Upload-Length: 0" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Id: empty-single" \
-hdr "Content-Type: application/offset+octet-stream" \
-hdr "Content-Length: 0"
rxresp
expect resp.status == 201
expect resp.http.Tus-Resumable == "1.0.0"
expect resp.http.Tus-Version == "1.0.0"
expect resp.http.Tus-Extension == "creation,creation-with-upload,expiration,termination,concatenation,checksum"
expect resp.http.Tus-Checksum-Algorithm == "crc32,icrc32,md5,rs,sha1,sha224,sha256,sha384,sha3_224,sha3_256,sha3_384,sha3_512,sha512"
expect resp.http.Tus-Max-Size == 3145
expect resp.http.Upload-Offset == 0
expect resp.http.Upload-Length == 0
expect resp.http.Upload-Expires ~ "GMT$"
expect resp.http.Location ~ "^http://localhost/empty-single"
} -start
# bad metadata fmt
client c10 {
txreq -method POST \
-hdr "Upload-Metadata: filename d29y!GRfZG9taW5hdGlvbl9wbGFuLnBkZg==,is_confidential" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream"
rxresp
expect resp.status == 400
txreq -method POST \
-hdr "Upload-Metadata: filename,filename" \
-hdr "Tus-Resumable: 1.0.0" \
-hdr "Content-Type: application/offset+octet-stream"
rxresp
expect resp.status == 400
} -start
client c1 -wait
client c2 -wait
client c3 -wait
client c4 -wait
client c5 -wait
client c6 -wait
client c7 -wait
client c8 -wait
client c9 -wait
client c10 -wait
varnishtest "test vmod-tus"
server s1 {
rxreq
txresp
} -start
varnish v1 -vcl+backend {
import tus;
sub vcl_deliver {
set resp.http.Hello = tus.hello();
}
} -start
client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.Hello == "vmod-tus"
} -run
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment