diff --git a/tests.json b/tests.json index c4cc3039..e5b846ce 100644 --- a/tests.json +++ b/tests.json @@ -2230,5 +2230,130 @@ "stderr": true, "returncode": 9 } - } + }, + { + "input": { + "arguments": [ + "http://test.org/?key=val", + "--replace", + "key=foo" + ] + }, + "expected": { + "stdout": "http://test.org/?key=foo\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "http://test.org/?that=thing&key=val", + "--replace", + "key=foo" + ] + }, + "expected": { + "stdout": "http://test.org/?that=thing&key=foo\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "http://test.org/?that=thing&key", + "--replace", + "key=foo" + ] + }, + "expected": { + "stdout": "http://test.org/?that=thing&key=foo\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "http://test.org/?that=thing&key=foo", + "--replace", + "key" + ] + }, + "expected": { + "stdout": "http://test.org/?that=thing&key\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "https://example.com?a=123&b=321&b=987", + "--replace", + "b=foo" + ] + }, + "expected": { + "stdout": "https://example.com/?a=123&b=foo\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "example.org/?quest=best", + "--replace", + "quest=%00", + "--json" + ] + }, + "expected": { + "stdout": [{ + "url": "http://example.org/?quest=%00", + "parts": { + "scheme": "http", + "host": "example.org", + "path": "/" + }, + "params": [ + { + "key": "quest", + "value": "\u0000" + } + ] + }], + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "example.com", + "--replace" + ] + }, + "expected": { + "stderr": "trurl error: No data passed to replace component\ntrurl error: Try trurl -h for help\n", + "stdout":"", + "returncode": 12 + } + }, + { + "input": { + "arguments": [ + "http://test.org/?that=thing", + "--force-replace", + "key=foo" + ] + }, + "expected": { + "stdout": "http://test.org/?that=thing&key=foo\n", + "stderr": "trurl note: key 'key' not in url, appending to query\n", + "returncode": 0 + } + } ] diff --git a/trurl.1 b/trurl.1 index 04f3c6b1..ef12f502 100644 --- a/trurl.1 +++ b/trurl.1 @@ -196,6 +196,15 @@ properly. Redirect the URL to this new location. The redirection is performed on the base URL, so, if no base URL is specified, no redirection will be performed. +.IP "--replace [data]" +Replaces a URL query. + +data can either take the form of a single value, or as a key/value pair in the +shape \fIfoo=bar\fP. If replace is called on an item that isn't in the list of +queries trurl will ignore that item. +.IP "--force-replace [data]" +Works the same as \fI--replace\fP, but trurl will append a missing query string if +it is not in the query list already. .IP "-s, --set [component][:]=[data]" Set this URL component. Setting blank string ("") will clear the component from the URL. diff --git a/trurl.c b/trurl.c index 458eead8..14b5d07a 100644 --- a/trurl.c +++ b/trurl.c @@ -129,6 +129,7 @@ static const struct var variables[] = { #define ERROR_BADURL 9 /* if --verify is set and the URL cannot parse */ #define ERROR_GET 10 /* bad --get syntax */ #define ERROR_ITER 11 /* bad --iterate syntax */ +#define ERROR_REPL 12 /* a --replace problem */ #ifndef SUPPORTS_URL_STRERROR /* provide a fake local mockup */ @@ -181,6 +182,8 @@ static void help(void) " --as-idn - encode hostnames in idn\n" " --query-separator [letter] - if something else than '&'\n" " --redirect [URL] - redirect to this\n" + " --replace [data] - replaces a query [data]\n" + " --force-replace [data] - appends a new query if not found\n" " -s, --set [component]=[data] - set component content\n" " --sort-query - alpha-sort the query pairs\n" " --trim [component]=[what] - trim component\n" @@ -262,6 +265,7 @@ struct option { struct curl_slist *set_list; struct curl_slist *trim_list; struct curl_slist *iter_list; + struct curl_slist *replace_list; const char *redirect; const char *qsep; const char *format; @@ -280,6 +284,7 @@ struct option { bool urlencode; bool end_of_options; bool quiet_warnings; + bool force_replace; /* -- stats -- */ unsigned int urls; @@ -311,6 +316,7 @@ static void trurl_cleanup_options(struct option *o) curl_slist_free_all(o->iter_list); curl_slist_free_all(o->append_query); curl_slist_free_all(o->trim_list); + curl_slist_free_all(o->replace_list); curl_slist_free_all(o->append_path); } @@ -458,6 +464,18 @@ static void trimadd(struct option *o, o->trim_list = n; } +static void replaceadd(struct option *o, + const char *replace_list) /* [component]=[data] */ +{ + struct curl_slist *n = NULL; + if(replace_list) + n = curl_slist_append(o->replace_list, replace_list); + else + errorf(o, ERROR_REPL, "No data passed to replace component"); + + if(n) + o->replace_list = n; +} static bool checkoptarg(struct option *o, const char *str, const char *given, const char *arg) @@ -574,6 +592,15 @@ static int getarg(struct option *o, o->urlencode = true; else if(!strcmp("--quiet", flag)) o->quiet_warnings = true; + else if(!strcmp("--replace", flag)) { + replaceadd(o, arg); + *usedarg = true; + } + else if(!strcmp("--force-replace", flag)) { + replaceadd(o, arg); + o->force_replace = true; + *usedarg = true; + } else return 1; /* unrecognized option */ return 0; @@ -1222,6 +1249,60 @@ static void sortquery(struct option *o) } } +static void replace(struct option *o) +{ + struct curl_slist *node; + for(node = o->replace_list; node; node = node->next) { + struct string key; + struct string value; + bool replaced = false; + int i; + key.str = node->data; + value.str = strchr(key.str, '='); + if(value.str) { + key.len = value.str++ - key.str; + value.len = strlen(value.str); + } + else { + key.len = strlen(key.str); + value.str = NULL; + value.len = 0; + } + for(i = 0; i < nqpairs; i++) { + char *q = qpairs[i].str; + /* not the correct query, move on */ + if(strncmp(q, key.str, key.len)) + continue; + free(qpairs[i].str); + curl_free(qpairsdec[i].str); + /* this is a duplicate remove it. */ + if(replaced) { + qpairs[i].len = 0; + qpairs[i].str = strdup(""); + qpairsdec[i].len = 0; + qpairsdec[i].str = strdup(""); + continue; + } + struct string *pdec = + memdupdec(key.str, key.len + value.len + 1, o->jsonout); + struct string *p = memdupzero(key.str, key.len + value.len + 1); + qpairs[i].len = p->len; + qpairs[i].str = p->str; + qpairsdec[i].len = pdec->len; + qpairsdec[i].str = pdec->str; + free(pdec); + free(p); + replaced = true; + } + + if(!replaced && o->force_replace) { + trurl_warnf(o, "key '%.*s' not in url, appending to query", + (int) (key.len), + key.str); + addqpair(key.str, strlen(key.str), o->jsonout); + } + } +} static CURLUcode seturl(struct option *o, CURLU *uh, const char *url) { return curl_url_set(uh, CURLUPART_URL, url, @@ -1371,6 +1452,9 @@ static void singleurl(struct option *o, /* trim parts */ trim(o); + /* replace parts */ + replace(o); + /* append query segments */ for(p = o->append_query; p; p = p->next) { addqpair(p->data, strlen(p->data), o->jsonout);