diff options
author | Zhidao HONG <z.hong@f5.com> | 2021-09-30 22:17:28 +0800 |
---|---|---|
committer | Zhidao HONG <z.hong@f5.com> | 2021-09-30 22:17:28 +0800 |
commit | c5220944d2acdb912c129fc82ac8a83d24e9845d (patch) | |
tree | 8cea32d295c7bde273cfe3a14b6044463aaec155 | |
parent | 37144d68499217d086c26a2030e6e9f6f7342152 (diff) | |
download | unit-c5220944d2acdb912c129fc82ac8a83d24e9845d.tar.gz unit-c5220944d2acdb912c129fc82ac8a83d24e9845d.tar.bz2 |
Static: variables in the "share" option.
This commit supports variable in the "share" option, the finding path to
file serve is the value from "share". An example:
{
"share": "/www/data/static$uri"
}
-rw-r--r-- | docs/changes.xml | 13 | ||||
-rw-r--r-- | src/nxt_conf_validation.c | 1 | ||||
-rw-r--r-- | src/nxt_http_static.c | 120 | ||||
-rw-r--r-- | test/test_static.py | 4 | ||||
-rw-r--r-- | test/test_static_chroot.py | 18 | ||||
-rw-r--r-- | test/test_static_fallback.py | 6 | ||||
-rw-r--r-- | test/test_static_mount.py | 14 | ||||
-rw-r--r-- | test/test_static_symlink.py | 12 | ||||
-rw-r--r-- | test/test_static_types.py | 37 |
9 files changed, 135 insertions, 90 deletions
diff --git a/docs/changes.xml b/docs/changes.xml index c547633a..1e1bff0a 100644 --- a/docs/changes.xml +++ b/docs/changes.xml @@ -31,6 +31,19 @@ NGINX Unit updated to 1.26.0. date="" time="" packager="Andrei Belov <defan@nginx.com>"> +<change type="change"> +<para> +the "share" option now specifies the entire path to the files it serves, +rather than a document root directory to be prepended to the request URI. +</para> +</change> + +<change type="feature"> +<para> +variables support in the "share" option. +</para> +</change> + <change type="feature"> <para> variables support in the "chroot" option. diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 7d599938..23e6d823 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -634,6 +634,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = { { .name = nxt_string("share"), .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_VAR, }, { .name = nxt_string("types"), .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index 263ec9db..c610bbf7 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -8,10 +8,11 @@ typedef struct { - nxt_str_t share; + nxt_var_t *share; #if (NXT_HAVE_OPENAT2) nxt_var_t *chroot; nxt_uint_t resolve; + u_char *fname; #endif nxt_http_route_rule_t *types; uint8_t is_const; /* 1 bit */ @@ -20,6 +21,7 @@ typedef struct { typedef struct { nxt_http_action_t *action; + nxt_str_t share; #if (NXT_HAVE_OPENAT2) nxt_str_t chroot; #endif @@ -59,7 +61,7 @@ nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_http_action_t *action, nxt_http_action_conf_t *acf) { nxt_mp_t *mp; - nxt_str_t *str, value; + nxt_str_t str; nxt_http_static_conf_t *conf; mp = tmcf->router_conf->mem_pool; @@ -72,17 +74,19 @@ nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, action->handler = nxt_http_static; action->u.conf = conf; - nxt_conf_get_string(acf->share, &value); + nxt_conf_get_string(acf->share, &str); - str = nxt_str_dup(mp, &conf->share, &value); - if (nxt_slow_path(str == NULL)) { + conf->share = nxt_var_compile(&str, mp, 1); + if (nxt_slow_path(conf->share == NULL)) { return NXT_ERROR; } - conf->is_const = 1; + conf->is_const = nxt_var_is_const(conf->share); #if (NXT_HAVE_OPENAT2) if (acf->chroot.length > 0) { + nxt_str_t chr, shr; + if (nxt_is_var(&acf->chroot)) { conf->is_const = 0; } @@ -91,6 +95,13 @@ nxt_http_static_init(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (nxt_slow_path(conf->chroot == NULL)) { return NXT_ERROR; } + + if (conf->is_const) { + nxt_var_raw(conf->chroot, &chr); + nxt_var_raw(conf->share, &shr); + + conf->fname = nxt_http_static_chroot_match(chr.start, shr.start); + } } if (acf->follow_symlinks != NULL @@ -155,7 +166,12 @@ nxt_http_static(nxt_task_t *task, nxt_http_request_t *r, conf = action->u.conf; -#if (NXT_DEBUG && NXT_HAVE_OPENAT2) +#if (NXT_DEBUG) + nxt_str_t shr; + + nxt_var_raw(conf->share, &shr); + +#if (NXT_HAVE_OPENAT2) nxt_str_t chr; if (conf->chroot != NULL) { @@ -165,11 +181,11 @@ nxt_http_static(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&chr, ""); } - nxt_debug(task, "http static: \"%V\" (chroot: \"%V\")", &conf->share, &chr); - + nxt_debug(task, "http static: \"%V\" (chroot: \"%V\")", &shr, &chr); #else - nxt_debug(task, "http static: \"%V\"", &conf->share); + nxt_debug(task, "http static: \"%V\"", &shr); #endif +#endif /* NXT_DEBUG */ ctx = nxt_mp_zget(r->mem_pool, sizeof(nxt_http_static_ctx_t)); if (nxt_slow_path(ctx == NULL)) { @@ -180,6 +196,8 @@ nxt_http_static(nxt_task_t *task, nxt_http_request_t *r, ctx->need_body = need_body; if (conf->is_const) { + nxt_var_raw(conf->share, &ctx->share); + #if (NXT_HAVE_OPENAT2) if (conf->chroot != NULL) { nxt_var_raw(conf->chroot, &ctx->chroot); @@ -194,8 +212,12 @@ nxt_http_static(nxt_task_t *task, nxt_http_request_t *r, goto fail; } + nxt_var_query(task, r->var_query, conf->share, &ctx->share); + #if (NXT_HAVE_OPENAT2) - nxt_var_query(task, r->var_query, conf->chroot, &ctx->chroot); + if (conf->chroot != NULL) { + nxt_var_query(task, r->var_query, conf->chroot, &ctx->chroot); + } #endif nxt_var_query_resolve(task, r->var_query, ctx, @@ -220,7 +242,7 @@ nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data) struct tm tm; nxt_buf_t *fb; nxt_int_t ret; - nxt_str_t index, exten, *mtype; + nxt_str_t *shr, exten, *mtype; nxt_uint_t level; nxt_file_t *f, file; nxt_file_info_t fi; @@ -233,56 +255,58 @@ nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data) nxt_http_static_ctx_t *ctx; nxt_http_static_conf_t *conf; + static nxt_str_t index = nxt_string("index.html"); + r = obj; ctx = data; action = ctx->action; conf = action->u.conf; - - if (r->path->start[r->path->length - 1] == '/') { - /* TODO: dynamic index setting. */ - nxt_str_set(&index, "index.html"); - nxt_str_set(&exten, ".html"); - - } else { - nxt_str_set(&index, ""); - nxt_str_null(&exten); - } + rtcf = r->conf->socket_conf->router_conf; f = NULL; + mtype = NULL; status = NXT_HTTP_INTERNAL_SERVER_ERROR; - rtcf = r->conf->socket_conf->router_conf; + shr = &ctx->share; - mtype = NULL; + if (shr->start[shr->length - 1] == '/') { + /* TODO: dynamic index setting. */ + nxt_str_set(&exten, ".html"); - if (conf->types != NULL && exten.start == NULL) { - nxt_http_static_extract_extension(r->path, &exten); - mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten); + length = shr->length + index.length; - ret = nxt_http_route_test_rule(r, conf->types, mtype->start, - mtype->length); - if (nxt_slow_path(ret == NXT_ERROR)) { + fname = nxt_mp_nget(r->mem_pool, length + 1); + if (nxt_slow_path(fname == NULL)) { goto fail; } - if (ret == 0) { - status = NXT_HTTP_FORBIDDEN; - goto fail; - } - } + p = fname; + p = nxt_cpymem(p, shr->start, shr->length); + p = nxt_cpymem(p, index.start, index.length); + *p = '\0'; - length = conf->share.length + r->path->length + index.length; + } else { + if (conf->types == NULL) { + nxt_str_null(&exten); - fname = nxt_mp_nget(r->mem_pool, length + 1); - if (nxt_slow_path(fname == NULL)) { - goto fail; - } + } else { + nxt_http_static_extract_extension(shr, &exten); + mtype = nxt_http_static_mtype_get(&rtcf->mtypes_hash, &exten); + + ret = nxt_http_route_test_rule(r, conf->types, mtype->start, + mtype->length); + if (nxt_slow_path(ret == NXT_ERROR)) { + goto fail; + } - p = fname; - p = nxt_cpymem(p, conf->share.start, conf->share.length); - p = nxt_cpymem(p, r->path->start, r->path->length); - p = nxt_cpymem(p, index.start, index.length); - *p = '\0'; + if (ret == 0) { + status = NXT_HTTP_FORBIDDEN; + goto fail; + } + } + + fname = ctx->share.start; + } nxt_memzero(&file, sizeof(nxt_file_t)); @@ -299,7 +323,9 @@ nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data) if (chr->length > 0) { resolve |= RESOLVE_IN_ROOT; - fname = nxt_http_static_chroot_match(chr->start, file.name); + fname = conf->is_const + ? conf->fname + : nxt_http_static_chroot_match(chr->start, file.name); if (fname != NULL) { file.name = chr->start; @@ -460,7 +486,7 @@ nxt_http_static_send_ready(nxt_task_t *task, void *obj, void *data) - p; if (exten.start == NULL) { - nxt_http_static_extract_extension(r->path, &exten); + nxt_http_static_extract_extension(shr, &exten); } if (mtype == NULL) { diff --git a/test/test_static.py b/test/test_static.py index 669e265d..fa0a81a2 100644 --- a/test/test_static.py +++ b/test/test_static.py @@ -28,7 +28,9 @@ class TestStatic(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": option.temp_dir + "/assets"}}], + "routes": [ + {"action": {"share": option.temp_dir + "/assets$uri"}} + ], "settings": { "http": { "static": { diff --git a/test/test_static_chroot.py b/test/test_static_chroot.py index f9bc93a8..aa2952a1 100644 --- a/test/test_static_chroot.py +++ b/test/test_static_chroot.py @@ -21,7 +21,7 @@ class TestStaticChroot(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], } ) @@ -31,7 +31,7 @@ class TestStaticChroot(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir", }, 'routes/0/action', @@ -49,7 +49,7 @@ class TestStaticChroot(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir", }, 'routes/0/action', @@ -59,7 +59,8 @@ class TestStaticChroot(TestApplicationProto): def test_static_chroot_empty(self, temp_dir): assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": ""}, 'routes/0/action', + {"share": temp_dir + "/assets$uri", "chroot": ""}, + 'routes/0/action', ), 'configure chroot empty absolute' assert ( @@ -67,7 +68,7 @@ class TestStaticChroot(TestApplicationProto): ), 'chroot empty absolute' assert 'success' in self.conf( - {"share": ".", "chroot": ""}, 'routes/0/action', + {"share": ".$uri", "chroot": ""}, 'routes/0/action', ), 'configure chroot empty relative' assert ( @@ -79,19 +80,20 @@ class TestStaticChroot(TestApplicationProto): pytest.skip('does\'t work under root') assert 'success' in self.conf( - {"share": temp_dir + "/assets", "chroot": "."}, 'routes/0/action', + {"share": temp_dir + "/assets$uri", "chroot": "."}, + 'routes/0/action', ), 'configure relative chroot' assert self.get(url='/dir/file')['status'] == 403, 'relative chroot' assert 'success' in self.conf( - {"share": "."}, 'routes/0/action', + {"share": ".$uri"}, 'routes/0/action', ), 'configure relative share' assert self.get(url=self.test_path)['status'] == 200, 'relative share' assert 'success' in self.conf( - {"share": ".", "chroot": "."}, 'routes/0/action', + {"share": ".$uri", "chroot": "."}, 'routes/0/action', ), 'configure relative' assert self.get(url=self.test_path)['status'] == 200, 'relative' diff --git a/test/test_static_fallback.py b/test/test_static_fallback.py index dc9056b9..4cfebcec 100644 --- a/test/test_static_fallback.py +++ b/test/test_static_fallback.py @@ -23,7 +23,7 @@ class TestStaticFallback(TestApplicationProto): "*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}, }, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, } ) @@ -50,7 +50,7 @@ class TestStaticFallback(TestApplicationProto): def test_static_fallback_valid_path(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "fallback": {"return": 200}} + {"share": temp_dir + "/assets$uri", "fallback": {"return": 200}} ) resp = self.get() assert resp['status'] == 200, 'fallback status' @@ -83,7 +83,7 @@ class TestStaticFallback(TestApplicationProto): def test_static_fallback_share(self, temp_dir): self.action_update( - {"share": "/blah", "fallback": {"share": temp_dir + "/assets"},} + {"share": "/blah", "fallback": {"share": temp_dir + "/assets$uri"},} ) resp = self.get() diff --git a/test/test_static_mount.py b/test/test_static_mount.py index 570f6439..fb4fbe4b 100644 --- a/test/test_static_mount.py +++ b/test/test_static_mount.py @@ -44,7 +44,7 @@ class TestStaticMount(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets/dir"}}], + "routes": [{"action": {"share": temp_dir + "/assets/dir$uri"}}], } ) @@ -72,14 +72,14 @@ class TestStaticMount(TestApplicationProto): assert resp['body'] == 'mount' assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": False}, + {"share": temp_dir + "/assets/dir$uri", "traverse_mounts": False}, 'routes/0/action', ), 'configure mount disable' assert self.get(url='/mount/')['status'] == 403 assert 'success' in self.conf( - {"share": temp_dir + "/assets/dir", "traverse_mounts": True}, + {"share": temp_dir + "/assets/dir$uri", "traverse_mounts": True}, 'routes/0/action', ), 'configure mount enable' @@ -97,14 +97,14 @@ class TestStaticMount(TestApplicationProto): { "match": {"method": "HEAD"}, "action": { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "traverse_mounts": False, }, }, { "match": {"method": "GET"}, "action": { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "traverse_mounts": True, }, }, @@ -120,7 +120,7 @@ class TestStaticMount(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "chroot": temp_dir + "/assets", }, 'routes/0/action', @@ -130,7 +130,7 @@ class TestStaticMount(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets/dir", + "share": temp_dir + "/assets/dir$uri", "chroot": temp_dir + "/assets", "traverse_mounts": False, }, diff --git a/test/test_static_symlink.py b/test/test_static_symlink.py index 35eb402a..e0fd7277 100644 --- a/test/test_static_symlink.py +++ b/test/test_static_symlink.py @@ -18,7 +18,7 @@ class TestStaticSymlink(TestApplicationProto): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], } ) @@ -33,14 +33,14 @@ class TestStaticSymlink(TestApplicationProto): assert self.get(url='/link/file')['status'] == 200, 'symlink file' assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": False}, + {"share": temp_dir + "/assets$uri", "follow_symlinks": False}, 'routes/0/action', ), 'configure symlink disable' assert self.get(url='/link/file')['status'] == 403, 'symlink disabled' assert 'success' in self.conf( - {"share": temp_dir + "/assets", "follow_symlinks": True}, + {"share": temp_dir + "/assets$uri", "follow_symlinks": True}, 'routes/0/action', ), 'configure symlink enable' @@ -56,14 +56,14 @@ class TestStaticSymlink(TestApplicationProto): { "match": {"method": "HEAD"}, "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "follow_symlinks": False, }, }, { "match": {"method": "GET"}, "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "follow_symlinks": True, }, }, @@ -85,7 +85,7 @@ class TestStaticSymlink(TestApplicationProto): assert 'success' in self.conf( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "chroot": temp_dir + "/assets/dir/dir", }, 'routes/0/action', diff --git a/test/test_static_types.py b/test/test_static_types.py index 20defddf..41ab7368 100644 --- a/test/test_static_types.py +++ b/test/test_static_types.py @@ -22,7 +22,7 @@ class TestStaticTypes(TestApplicationProto): "*:7080": {"pass": "routes"}, "*:7081": {"pass": "routes"}, }, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, } ) @@ -36,39 +36,39 @@ class TestStaticTypes(TestApplicationProto): assert resp['body'] == body, 'body' def test_static_types_basic(self, temp_dir): - self.action_update({"share": temp_dir + "/assets"}) + self.action_update({"share": temp_dir + "/assets$uri"}) self.check_body('/index.html', 'index') self.check_body('/file.xml', '.xml') self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} + {"share": temp_dir + "/assets$uri", "types": "application/xml"} ) self.check_body('/file.xml', '.xml') self.action_update( - {"share": temp_dir + "/assets", "types": ["application/xml"]} + {"share": temp_dir + "/assets$uri", "types": ["application/xml"]} ) self.check_body('/file.xml', '.xml') - self.action_update({"share": temp_dir + "/assets", "types": [""]}) + self.action_update({"share": temp_dir + "/assets$uri", "types": [""]}) assert self.get(url='/file.xml')['status'] == 403, 'no mtype' def test_static_types_wildcard(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["application/*"]} + {"share": temp_dir + "/assets$uri", "types": ["application/*"]} ) self.check_body('/file.xml', '.xml') assert self.get(url='/file.mp4')['status'] == 403, 'app * mtype mp4' self.action_update( - {"share": temp_dir + "/assets", "types": ["video/*"]} + {"share": temp_dir + "/assets$uri", "types": ["video/*"]} ) assert self.get(url='/file.xml')['status'] == 403, 'video * mtype xml' self.check_body('/file.mp4', '.mp4') def test_static_types_negation(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["!application/xml"]} + {"share": temp_dir + "/assets$uri", "types": ["!application/xml"]} ) assert self.get(url='/file.xml')['status'] == 403, 'forbidden negation' self.check_body('/file.mp4', '.mp4') @@ -76,7 +76,7 @@ class TestStaticTypes(TestApplicationProto): # sorting negation self.action_update( { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "types": ["!video/*", "image/png", "!image/jpg"], } ) @@ -86,7 +86,7 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_regex(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["~text/(html|plain)"]} + {"share": temp_dir + "/assets$uri", "types": ["~text/(html|plain)"]} ) assert self.get(url='/file.php')['status'] == 403, 'regex fail' self.check_body('/file.html', '.html') @@ -94,7 +94,7 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_case(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": ["!APpliCaTiOn/xMl"]} + {"share": temp_dir + "/assets$uri", "types": ["!APpliCaTiOn/xMl"]} ) self.check_body('/file.mp4', '.mp4') assert ( @@ -102,7 +102,7 @@ class TestStaticTypes(TestApplicationProto): ), 'mixed case xml negation' self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/mp4"]} + {"share": temp_dir + "/assets$uri", "types": ["vIdEo/mp4"]} ) assert self.get(url='/file.mp4')['status'] == 200, 'mixed case' assert ( @@ -110,7 +110,7 @@ class TestStaticTypes(TestApplicationProto): ), 'mixed case video negation' self.action_update( - {"share": temp_dir + "/assets", "types": ["vIdEo/*"]} + {"share": temp_dir + "/assets$uri", "types": ["vIdEo/*"]} ) self.check_body('/file.mp4', '.mp4') assert ( @@ -126,7 +126,7 @@ class TestStaticTypes(TestApplicationProto): }, { "action": { - "share": temp_dir + "/assets", + "share": temp_dir + "/assets$uri", "types": ["!application/x-httpd-php"], "fallback": {"proxy": "http://127.0.0.1:7081"}, } @@ -140,17 +140,18 @@ class TestStaticTypes(TestApplicationProto): def test_static_types_index(self, temp_dir): self.action_update( - {"share": temp_dir + "/assets", "types": "application/xml"} + {"share": temp_dir + "/assets$uri", "types": "application/xml"} ) self.check_body('/', 'index') self.check_body('/file.xml', '.xml') + assert self.get(url='/index.html')['status'] == 403, 'forbidden mtype' assert self.get(url='/file.mp4')['status'] == 403, 'forbidden mtype' def test_static_types_custom_mime(self, temp_dir): self._load_conf( { "listeners": {"*:7080": {"pass": "routes"}}, - "routes": [{"action": {"share": temp_dir + "/assets"}}], + "routes": [{"action": {"share": temp_dir + "/assets$uri"}}], "applications": {}, "settings": { "http": { @@ -160,10 +161,10 @@ class TestStaticTypes(TestApplicationProto): } ) - self.action_update({"share": temp_dir + "/assets", "types": [""]}) + self.action_update({"share": temp_dir + "/assets$uri", "types": [""]}) assert self.get(url='/file')['status'] == 403, 'forbidden custom mime' self.action_update( - {"share": temp_dir + "/assets", "types": ["test/mime-type"]} + {"share": temp_dir + "/assets$uri", "types": ["test/mime-type"]} ) self.check_body('/file', '') |