Merge pull request #20172 from poettering/import-util-tweaks
Import-util: URL processing tweaks
This commit is contained in:
+85
-24
@@ -14,53 +14,114 @@
|
||||
#include "string-table.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int import_url_last_component(const char *url, char **ret) {
|
||||
const char *e, *p;
|
||||
char *s;
|
||||
static const char *skip_protocol_and_hostname(const char *url) {
|
||||
const char *d;
|
||||
size_t n;
|
||||
|
||||
e = strchrnul(url, '?');
|
||||
/* A very very lenient implementation of RFC3986 Section 3.2 */
|
||||
|
||||
while (e > url && e[-1] == '/')
|
||||
/* Find colon separating protocol and hostname */
|
||||
d = strchr(url, ':');
|
||||
if (!d || url == d)
|
||||
return NULL;
|
||||
d++;
|
||||
|
||||
/* Skip slashes after colon */
|
||||
d += strspn(d, "/");
|
||||
|
||||
/* Skip everything till next slash or end */
|
||||
n = strcspn(d, "/?#");
|
||||
if (n == 0)
|
||||
return NULL;
|
||||
|
||||
return d + n;
|
||||
}
|
||||
|
||||
int import_url_last_component(
|
||||
const char *url,
|
||||
char **ret) {
|
||||
|
||||
const char *e, *p, *h;
|
||||
|
||||
/* This extracts the last path component of the specified URI, i.e. the last non-empty substrings
|
||||
* between two "/" characters. This ignores "Query" and "Fragment" suffixes (as per RFC3986). */
|
||||
|
||||
h = skip_protocol_and_hostname(url);
|
||||
if (!h)
|
||||
return -EINVAL;
|
||||
|
||||
e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
|
||||
|
||||
while (e > h && e[-1] == '/') /* Eat trailing slashes */
|
||||
e--;
|
||||
|
||||
p = e;
|
||||
while (p > url && p[-1] != '/')
|
||||
while (p > h && p[-1] != '/') /* Find component before that */
|
||||
p--;
|
||||
|
||||
if (e <= p)
|
||||
return -EINVAL;
|
||||
if (e <= p) /* Empty component? */
|
||||
return -EADDRNOTAVAIL;
|
||||
|
||||
s = strndup(p, e - p);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
if (ret) {
|
||||
char *s;
|
||||
|
||||
s = strndup(p, e - p);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
*ret = s;
|
||||
}
|
||||
|
||||
*ret = s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
|
||||
const char *e;
|
||||
int import_url_change_suffix(
|
||||
const char *url,
|
||||
size_t n_drop_components,
|
||||
const char *suffix,
|
||||
char **ret) {
|
||||
|
||||
const char *e, *h;
|
||||
char *s;
|
||||
|
||||
assert(url);
|
||||
assert(ret);
|
||||
|
||||
e = strchrnul(url, '?');
|
||||
/* This drops the specified number of path components of the specified URI, i.e. the specified number
|
||||
* of non-empty substring between two "/" characters from the end of the string, and then append the
|
||||
* specified suffix instead. Before doing all this it chops off the "Query" and "Fragment" suffixes
|
||||
* (they are *not* readded to the final URL). Note that n_drop_components may be 0 (in which case the
|
||||
* component are simply added to the end). The suffix may be specified as NULL or empty string in
|
||||
* which case nothing is appended, only the specified number of components chopped off. Note that the
|
||||
* function may be called with n_drop_components == 0 and suffix == NULL, in which case the "Query"
|
||||
* and "Fragment" is chopped off, and ensured the URL ends in a single "/", and that's it. */
|
||||
|
||||
while (e > url && e[-1] == '/')
|
||||
e--;
|
||||
|
||||
while (e > url && e[-1] != '/')
|
||||
e--;
|
||||
|
||||
if (e <= url)
|
||||
h = skip_protocol_and_hostname(url);
|
||||
if (!h)
|
||||
return -EINVAL;
|
||||
|
||||
s = new(char, (e - url) + strlen(suffix) + 1);
|
||||
e = h + strcspn(h, "?#"); /* Cut off "Query" and "Fragment" */
|
||||
|
||||
while (e > h && e[-1] == '/') /* Eat trailing slashes */
|
||||
e--;
|
||||
|
||||
/* Drop the specified number of components from the end. Note that this is pretty lenient: if there
|
||||
* are less component we silently drop those and then append the suffix to the top. */
|
||||
while (n_drop_components > 0) {
|
||||
while (e > h && e[-1] != '/') /* Eat last word (we don't mind if empty) */
|
||||
e--;
|
||||
|
||||
while (e > h && e[-1] == '/') /* Eat slashes before the last word */
|
||||
e--;
|
||||
|
||||
n_drop_components--;
|
||||
}
|
||||
|
||||
s = new(char, (e - url) + 1 + strlen_ptr(suffix) + 1);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
strcpy(mempcpy(s, url, e - url), suffix);
|
||||
strcpy(stpcpy(mempcpy(s, url, e - url), "/"), strempty(suffix));
|
||||
*ret = s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,16 @@ typedef enum ImportVerify {
|
||||
} ImportVerify;
|
||||
|
||||
int import_url_last_component(const char *url, char **ret);
|
||||
int import_url_change_last_component(const char *url, const char *suffix, char **ret);
|
||||
|
||||
int import_url_change_suffix(const char *url, size_t n_drop_components, const char *suffix, char **ret);
|
||||
|
||||
static inline int import_url_change_last_component(const char *url, const char *suffix, char **ret) {
|
||||
return import_url_change_suffix(url, 1, suffix, ret);
|
||||
}
|
||||
|
||||
static inline int import_url_append_component(const char *url, const char *suffix, char **ret) {
|
||||
return import_url_change_suffix(url, 0, suffix, ret);
|
||||
}
|
||||
|
||||
const char* import_verify_to_string(ImportVerify v) _const_;
|
||||
ImportVerify import_verify_from_string(const char *s) _pure_;
|
||||
|
||||
@@ -250,6 +250,8 @@ tests += [
|
||||
|
||||
[['src/test/test-sysctl-util.c']],
|
||||
|
||||
[['src/test/test-import-util.c']],
|
||||
|
||||
[['src/test/test-user-record.c']],
|
||||
|
||||
[['src/test/test-user-util.c']],
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "import-util.h"
|
||||
#include "log.h"
|
||||
#include "string-util.h"
|
||||
#include "tests.h"
|
||||
|
||||
static void test_import_url_last_component_one(const char *input, const char *output, int ret) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
assert_se(import_url_last_component(input, &s) == ret);
|
||||
assert_se(streq_ptr(output, s));
|
||||
}
|
||||
|
||||
static void test_import_url_last_component(void) {
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux/", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/", "waldo", 0);
|
||||
test_import_url_last_component_one("https://foobar/", NULL, -EADDRNOTAVAIL);
|
||||
test_import_url_last_component_one("https://foobar", NULL, -EADDRNOTAVAIL);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux?foo=bar", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux/?foo=bar#piep", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux/#piep", "quux", 0);
|
||||
test_import_url_last_component_one("https://foobar/waldo/quux#piep", "quux", 0);
|
||||
test_import_url_last_component_one("https://", NULL, -EINVAL);
|
||||
test_import_url_last_component_one("", NULL, -EINVAL);
|
||||
test_import_url_last_component_one(":", NULL, -EINVAL);
|
||||
test_import_url_last_component_one(":/", NULL, -EINVAL);
|
||||
test_import_url_last_component_one("x:/", NULL, -EINVAL);
|
||||
test_import_url_last_component_one("x:y", NULL, -EADDRNOTAVAIL);
|
||||
test_import_url_last_component_one("x:y/z", "z", 0);
|
||||
}
|
||||
|
||||
static void test_import_url_change_suffix_one(const char *input, size_t n, const char *suffix, const char *output, int ret) {
|
||||
_cleanup_free_ char *s = NULL;
|
||||
|
||||
assert_se(import_url_change_suffix(input, n, suffix, &s) == ret);
|
||||
assert_se(streq_ptr(output, s));
|
||||
}
|
||||
|
||||
static void test_import_url_change_suffix(void) {
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux", 1, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux/", 1, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief", 1, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux///?mief#opopo", 1, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 2, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux/quff/", 2, "wuff", "https://foobar/waldo/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux/quff", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://foobar/waldo/quux/quff?aa?bb##4", 0, "wuff", "https://foobar/waldo/quux/quff/wuff", 0);
|
||||
test_import_url_change_suffix_one("https://", 0, "wuff", NULL, -EINVAL);
|
||||
test_import_url_change_suffix_one("", 0, "wuff", NULL, -EINVAL);
|
||||
test_import_url_change_suffix_one(":", 0, "wuff", NULL, -EINVAL);
|
||||
test_import_url_change_suffix_one(":/", 0, "wuff", NULL, -EINVAL);
|
||||
test_import_url_change_suffix_one("x:/", 0, "wuff", NULL, -EINVAL);
|
||||
test_import_url_change_suffix_one("x:y", 0, "wuff", "x:y/wuff", 0);
|
||||
test_import_url_change_suffix_one("x:y/z", 0, "wuff", "x:y/z/wuff", 0);
|
||||
test_import_url_change_suffix_one("x:y/z/", 0, "wuff", "x:y/z/wuff", 0);
|
||||
test_import_url_change_suffix_one("x:y/z/", 1, "wuff", "x:y/wuff", 0);
|
||||
test_import_url_change_suffix_one("x:y/z/", 2, "wuff", "x:y/wuff", 0);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
|
||||
test_setup_logging(LOG_INFO);
|
||||
|
||||
test_import_url_last_component();
|
||||
test_import_url_change_suffix();
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user