aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libutil/http.c114
-rw-r--r--test/lua/unit/url.lua28
-rw-r--r--test/rspamd_test_suite.c2
3 files changed, 124 insertions, 20 deletions
diff --git a/src/libutil/http.c b/src/libutil/http.c
index 73db0d17a..fdc9d1294 100644
--- a/src/libutil/http.c
+++ b/src/libutil/http.c
@@ -3435,7 +3435,7 @@ rspamd_http_message_unref (struct rspamd_http_message *msg)
void
rspamd_http_normalize_path_inplace (gchar *path, gsize len, gsize *nlen)
{
- const gchar *p, *end, *c, *slash;
+ const gchar *p, *end, *slash = NULL, *dot = NULL;
gchar *o;
enum {
st_normal = 0,
@@ -3446,7 +3446,6 @@ rspamd_http_normalize_path_inplace (gchar *path, gsize len, gsize *nlen)
} state = st_normal;
p = path;
- c = path;
end = path + len;
o = path;
@@ -3455,11 +3454,11 @@ rspamd_http_normalize_path_inplace (gchar *path, gsize len, gsize *nlen)
case st_normal:
if (G_UNLIKELY (*p == '/')) {
state = st_got_slash;
- c = p;
+ slash = p;
}
else if (G_UNLIKELY (*p == '.')) {
state = st_got_dot;
- c = p;
+ dot = p;
}
else {
*o++ = *p;
@@ -3473,24 +3472,38 @@ rspamd_http_normalize_path_inplace (gchar *path, gsize len, gsize *nlen)
state = st_got_slash_slash;
}
else if (G_UNLIKELY (*p == '.')) {
+ dot = p;
state = st_got_dot;
}
else {
+ *o++ = '/';
*o++ = *p;
+ slash = NULL;
+ dot = NULL;
state = st_normal;
}
p ++;
break;
case st_got_slash_slash:
if (G_LIKELY (*p != '/')) {
- *o++ = *p;
+ slash = p - 1;
+ dot = NULL;
state = st_normal;
+ continue;
}
p ++;
break;
case st_got_dot:
if (G_UNLIKELY (*p == '/')) {
/* Remove any /./ or ./ paths */
+ if (((o > path && *(o - 1) != '/') || (o == path)) && slash) {
+ /* Preserve one slash */
+ *o++ = '/';
+ }
+
+ slash = p;
+ dot = NULL;
+ /* Ignore last slash */
state = st_normal;
}
else if (*p == '.') {
@@ -3499,52 +3512,117 @@ rspamd_http_normalize_path_inplace (gchar *path, gsize len, gsize *nlen)
}
else {
/* We have something like .some or /.some */
- if (p > c) {
- memcpy (o, c, p - c);
- o += p - c;
+ if (dot && p > dot) {
+ memmove (o, dot, p - dot);
+ o += p - dot;
}
+ slash = NULL;
+ dot = NULL;
state = st_normal;
+ continue;
}
+
p ++;
break;
case st_got_dot_dot:
if (*p == '/') {
/* We have something like /../ or ../ */
- if (*c == '/') {
+ if (slash) {
/* We need to remove the last component from o if it is there */
- slash = rspamd_memrchr (path, '/', o - path);
+ if (o > path + 2 && *(o - 1) == '/') {
+ slash = rspamd_memrchr (path, '/', o - path - 2);
+ }
+ else if (o > path + 1) {
+ slash = rspamd_memrchr (path, '/', o - path - 1);
+ }
+ else {
+ slash = NULL;
+ }
if (slash) {
o = (gchar *)slash;
}
- /* Otherwise we remove these dots */
- state = st_normal;
+ /* Otherwise we keep these dots */
+ slash = p;
+ state = st_got_slash;
}
else {
/* We have something like bla../, so we need to copy it as is */
- if (p > c) {
- memcpy (o, c, p - c);
- o += p - c;
+
+ if (slash) {
+ *o ++ = '/';
+ }
+ if (dot && p > dot) {
+ memcpy (o, dot, p - dot);
+ o += p - dot;
}
+ slash = NULL;
+ dot = NULL;
state = st_normal;
+ continue;
}
}
else {
/* We have something like ..bla or ... */
- if (p > c) {
- memcpy (o, c, p - c);
- o += p - c;
+ if (slash) {
+ *o ++ = '/';
}
+ if (dot && p > dot) {
+ memmove (o, dot, p - dot);
+ o += p - dot;
+ }
+
+ slash = NULL;
+ dot = NULL;
state = st_normal;
+ continue;
}
+
p ++;
break;
}
}
+ /* Leftover */
+ switch (state) {
+ case st_got_dot_dot:
+ /* Trailing .. */
+ if (slash) {
+ /* We need to remove the last component from o if it is there */
+ if (o > path + 2 && *(o - 1) == '/') {
+ slash = rspamd_memrchr (path, '/', o - path - 2);
+ }
+ else if (o > path + 1) {
+ slash = rspamd_memrchr (path, '/', o - path - 1);
+ }
+ else {
+ if (o == path) {
+ /* Corner case */
+ *o++ = '/';
+ }
+
+ slash = NULL;
+ }
+
+ if (slash) {
+ /* Remove last / */
+ o = (gchar *)slash;
+ }
+ }
+ break;
+ case st_got_slash:
+ *o++ = '/';
+ break;
+ default:
+ if (o > path + 1 && *(o - 1) == '/') {
+ o --;
+ }
+ break;
+ }
+
if (nlen) {
*nlen = (o - path);
}
diff --git a/test/lua/unit/url.lua b/test/lua/unit/url.lua
index de274425d..991c6d0d0 100644
--- a/test/lua/unit/url.lua
+++ b/test/lua/unit/url.lua
@@ -8,6 +8,7 @@ context("URL check functions", function()
ffi.cdef[[
void rspamd_url_init (const char *tld_file);
unsigned ottery_rand_range(unsigned top);
+ void rspamd_http_normalize_path_inplace(char *path, size_t len, size_t *nlen);
]]
local test_dir = string.gsub(debug.getinfo(1).source, "^@(.+/)[^/]+$", "%1")
@@ -119,4 +120,31 @@ context("URL check functions", function()
end
end
)
+ test("Normalize paths", function()
+ local cases = {
+ {"/././foo", "/foo"},
+ {"/a/b/c/./../../g", "/a/g"},
+ {"/./.foo", "/.foo"},
+ {"/foo/.", "/foo"},
+ {"/foo/./", "/foo"},
+ {"/foo/bar/..", "/foo"},
+ {"/foo/bar/../", "/foo/"},
+ {"/foo/..bar", "/foo/..bar"},
+ {"/foo/bar/../ton", "/foo/ton"},
+ {"/foo/bar/../ton/../../a", "/a"},
+ {"/foo/../../..", "/"},
+ {"/foo/../../../ton", "/ton"},
+ {"////../..", "/"},
+ }
+
+ for _,v in ipairs(cases) do
+ print(v[1])
+ local buf = ffi.new("uint8_t[?]", #v[1])
+ local sizbuf = ffi.new("size_t[1]")
+ ffi.copy(buf, v[1], #v[1])
+ ffi.C.rspamd_http_normalize_path_inplace(buf, #v[1], sizbuf)
+ local res = ffi.string(buf, tonumber(sizbuf[0]))
+ assert_equal(v[2], res, 'expected ' .. v[2] .. ' but got ' .. res .. ' in path ' .. v[1])
+ end
+ end)
end)
diff --git a/test/rspamd_test_suite.c b/test/rspamd_test_suite.c
index ddce88d3d..f3586e4f5 100644
--- a/test/rspamd_test_suite.c
+++ b/test/rspamd_test_suite.c
@@ -59,8 +59,6 @@ main (int argc, char **argv)
g_test_add_func ("/rspamd/aio", rspamd_async_test_func);
#endif
g_test_run ();
-
- g_mime_shutdown ();
rspamd_regexp_library_finalize ();
return 0;