Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package trurl for openSUSE:Factory checked in at 2024-02-22 20:59:45 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/trurl (Old) and /work/SRC/openSUSE:Factory/.trurl.new.1706 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "trurl" Thu Feb 22 20:59:45 2024 rev:10 rq:1149120 version:0.10 Changes: -------- --- /work/SRC/openSUSE:Factory/trurl/trurl.changes 2023-11-01 22:11:12.510396927 +0100 +++ /work/SRC/openSUSE:Factory/.trurl.new.1706/trurl.changes 2024-02-22 21:00:54.812422649 +0100 @@ -1,0 +2,10 @@ +Wed Feb 21 21:20:22 UTC 2024 - Martin Hauke <[email protected]> + +- Update to version 0.10 + Changes: + * add --replace + Bugfixes: + * fixed buffer overflows on %00 use + * enable more C compiler warnings and fix them + +------------------------------------------------------------------- Old: ---- trurl-0.9.tar.gz New: ---- trurl-0.10.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ trurl.spec ++++++ --- /var/tmp/diff_new_pack.BFz0Rc/_old 2024-02-22 21:00:55.780458039 +0100 +++ /var/tmp/diff_new_pack.BFz0Rc/_new 2024-02-22 21:00:55.784458186 +0100 @@ -1,8 +1,8 @@ # # spec file for package trurl # -# Copyright (c) 2023 SUSE LLC -# Copyright (c) 2023, Martin Hauke <[email protected]> +# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2023-2024, Martin Hauke <[email protected]> # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ Name: trurl -Version: 0.9 +Version: 0.10 Release: 0 Summary: Command line tool for URL parsing and manipulation License: MIT ++++++ trurl-0.9.tar.gz -> trurl-0.10.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/.github/workflows/curl-for-win.yml new/trurl-trurl-0.10/.github/workflows/curl-for-win.yml --- old/trurl-trurl-0.9/.github/workflows/curl-for-win.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/trurl-trurl-0.10/.github/workflows/curl-for-win.yml 2024-02-19 23:14:18.000000000 +0100 @@ -0,0 +1,56 @@ +# Copyright (C) Viktor Szakats. See LICENSE.md +# SPDX-License-Identifier: curl +--- +name: curl-for-win + +on: + push: + branches: ["master"] + pull_request: + branches: ["master"] + +permissions: {} + +env: + CW_GET: 'curl' + CW_MAP: '0' + CW_JOBS: '3' + CW_PKG_NODELETE: '1' + CW_PKG_FLATTEN: '1' + DOCKER_CONTENT_TRUST: '1' + +jobs: + win-llvm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + path: 'trurl' + fetch-depth: 8 + - name: 'build' + env: + CW_LLVM_MINGW_DL: '1' + CW_LLVM_MINGW_ONLY: '0' + CW_TURL_TEST: '1' + run: | + git clone --depth 1 https://github.com/curl/curl-for-win + mv curl-for-win/* . + export CW_CONFIG='-dev-zero-imap-osnotls-osnoidn-nohttp-nocurltool-win' + export CW_REVISION='${{ github.sha }}' + . ./_versions.sh + docker trust inspect --pretty "${DOCKER_IMAGE}" + time docker pull "${DOCKER_IMAGE}" + docker images --digests + time docker run --volume "$(pwd):$(pwd)" --workdir "$(pwd)" \ + --env-file <(env | grep -a -E \ + '^(CW_|GITHUB_)') \ + "${DOCKER_IMAGE}" \ + sh -c ./_ci-linux-debian.sh + + - name: 'list dependencies' + run: cat urls.txt + - uses: actions/upload-artifact@v3 + with: + name: 'trurl-windows' + retention-days: 5 + path: curl-*-*-*/trurl* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/.reuse/dep5 new/trurl-trurl-0.10/.reuse/dep5 --- old/trurl-trurl-0.9/.reuse/dep5 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/.reuse/dep5 2024-02-19 23:14:18.000000000 +0100 @@ -9,7 +9,7 @@ License: curl # Docs -Files: CONTRIBUTING.md README.md RELEASE-NOTES THANKS URL-QUIRKS.md +Files: CONTRIBUTING.md README.md RELEASE-NOTES THANKS URL-QUIRKS.md RELEASE-PROCEDURE.md Copyright: Daniel Stenberg, <[email protected]>, et al. License: curl diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/Makefile new/trurl-trurl-0.10/Makefile --- old/trurl-trurl-0.9/Makefile 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/Makefile 2024-02-19 23:14:18.000000000 +0100 @@ -24,8 +24,15 @@ TARGET = trurl OBJS = trurl.o -LDLIBS = $$(curl-config --libs) -CFLAGS += $$(curl-config --cflags) -W -Wall -Wshadow -Werror -pedantic -g -std=gnu99 +ifndef TRURL_IGNORE_CURL_CONFIG +LDLIBS += $$(curl-config --libs) +CFLAGS += $$(curl-config --cflags) +endif +CFLAGS += -W -Wall -Wshadow -Werror -pedantic +CFLAGS += -Wconversion -Wmissing-prototypes -Wwrite-strings -Wsign-compare -Wno-sign-conversion +ifndef NDEBUG +CFLAGS += -g +endif MANUAL = trurl.1 PREFIX ?= /usr/local @@ -36,9 +43,9 @@ PYTHON3 ?= python3 $(TARGET): $(OBJS) - $(CC) $(OBJS) -o $(TARGET) $(LDLIBS) $(LDFLAGS) + $(CC) $(OBJS) -o $(TARGET) $(LDFLAGS) $(LDLIBS) -trurl.o:trurl.c version.h +trurl.o: trurl.c version.h .PHONY: install install: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/RELEASE-NOTES new/trurl-trurl-0.10/RELEASE-NOTES --- old/trurl-trurl-0.9/RELEASE-NOTES 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/RELEASE-NOTES 2024-02-19 23:14:18.000000000 +0100 @@ -1,18 +1,16 @@ -trurl 0.9 +trurl 0.10 Changes since previous release - o add --as-idn and punycode to IDN conversion - o add --curl to only count as valid URLs supported by libcurl - o add vs2022 project files + o add --replace Bugfixes since previous release - o accept \* as a trim name to trim a literal asterisk name - o format null as \u0000 for --json - o run --trim query before --append query + o fixed buffer overflows on %00 use + o support compiling with old versions of Visual Studio + o enable more C compiler warnings and fix them + o ci: add Windows builds Contributors to this release: - Daniel Stenberg, Ehsan, Emanuele Torre, Jacob Mealey, Krishean Draconis, - Michael Ablassmeier, ç©ä¸¹å°¼ Dan Jacobson + Daniel Stenberg, Jacob Mealey, Jay Satiro, Michael Lass, Viktor Szakats diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/RELEASE-PROCEDURE.md new/trurl-trurl-0.10/RELEASE-PROCEDURE.md --- old/trurl-trurl-0.9/RELEASE-PROCEDURE.md 1970-01-01 01:00:00.000000000 +0100 +++ new/trurl-trurl-0.10/RELEASE-PROCEDURE.md 2024-02-19 23:14:18.000000000 +0100 @@ -0,0 +1,24 @@ +trurl release procedure - how to do a release +============================================== + +in the source code repo +----------------------- + +- edit `RELEASE-NOTES` to be accurate + +- edit `version.h` to contain the correct version + +- make sure all relevant changes are committed on the master branch + +- tag the git repo in this style: `git tag -a trurl-[version]` -a annotates + the tag + +- push the git commits and the new tag + +- Go to https://github.com/curl/trurl/tags and edit the tag as a release + Consider allowing it to make a discussion post about it. + +celebrate +--------- + +- suitable beverage intake is encouraged for the festivities diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/test.py new/trurl-trurl-0.10/test.py --- old/trurl-trurl-0.9/test.py 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/test.py 2024-02-19 23:14:18.000000000 +0100 @@ -59,8 +59,9 @@ class TestCase: - def __init__(self, testIndex, baseCmd, **testCase): + def __init__(self, testIndex, runnerCmd, baseCmd, **testCase): self.testIndex = testIndex + self.runnerCmd = runnerCmd self.baseCmd = baseCmd self.arguments = testCase["input"]["arguments"] self.expected = testCase["expected"] @@ -74,7 +75,10 @@ cmd = [self.baseCmd] args = self.arguments - if runWithValgrind: + if self.runnerCmd != "": + cmd = [self.runnerCmd] + args = [self.baseCmd] + self.arguments + elif runWithValgrind: cmd = [VALGRINDTEST] args = VALGRINDARGS + [self.baseCmd] + self.arguments @@ -96,6 +100,11 @@ # assume stderr is always going to be string stderr = output.stderr + # runners (e.g. wine) spill their own output into stderr, + # ignore stderr tests when using a runner. + if self.runnerCmd != "" and "stderr" in self.expected: + stderr = self.expected["stderr"] + self.commandOutput = CommandOutput(stdout, output.returncode, stderr) return True @@ -161,41 +170,53 @@ # the .exe on the end is necessary when using absolute paths if sys.platform == "win32" or sys.platform == "cygwin": baseCmd += ".exe" + + with open(path.join(baseDir, TESTFILE), "r") as file: + allTests = json.load(file) + testIndexesToRun = [] + + # if argv[1] exists and starts with int + cmdfilter = "" + testIndexesToRun = list(range(len(allTests))) + runWithValgrind = False + verboseDetail = False + runnerCmd = "" + + if argc > 1: + for arg in argv[1:]: + if arg[0].isnumeric(): + # run only test cases separated by "," + testIndexesToRun = [] + + for caseIndex in arg.split(","): + testIndexesToRun.append(int(caseIndex)) + elif arg == "--with-valgrind": + runWithValgrind = True + elif arg == "--verbose": + verboseDetail = True + elif arg.startswith("--trurl="): + baseCmd = arg[len("--trurl="):] + elif arg.startswith("--runner="): + runnerCmd = arg[len("--runner="):] + else: + cmdfilter = argv[1] + # check if the trurl executable exists if path.isfile(baseCmd): # get the version info for the feature list + args = ["--version"] + if runnerCmd != "": + cmd = [runnerCmd] + args = [baseCmd] + args + else: + cmd = [baseCmd] output = run( - [baseCmd, "--version"], + cmd + args, stdout=PIPE, stderr=PIPE, encoding="utf-8" ) features = output.stdout.split('\n')[1].split()[1:] - with open(path.join(baseDir, TESTFILE), "r") as file: - allTests = json.load(file) - testIndexesToRun = [] - - # if argv[1] exists and starts with int - cmdfilter = "" - testIndexesToRun = list(range(len(allTests))) - runWithValgrind = False - verboseDetail = False - - if argc > 1: - for arg in argv[1:]: - if arg[0].isnumeric(): - # run only test cases separated by "," - testIndexesToRun = [] - - for caseIndex in arg.split(","): - testIndexesToRun.append(int(caseIndex)) - elif arg == "--with-valgrind": - runWithValgrind = True - elif arg == "--verbose": - verboseDetail = True - else: - cmdfilter = argv[1] - numTestsFailed = 0 numTestsPassed = 0 numTestsSkipped = 0 @@ -208,7 +229,7 @@ numTestsSkipped += 1 continue - test = TestCase(testIndex + 1, baseCmd, **allTests[testIndex]) + test = TestCase(testIndex + 1, runnerCmd, baseCmd, **allTests[testIndex]) if test.runCommand(cmdfilter, runWithValgrind): if test.test(): # passed diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/tests.json new/trurl-trurl-0.10/tests.json --- old/trurl-trurl-0.9/tests.json 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/tests.json 2024-02-19 23:14:18.000000000 +0100 @@ -260,13 +260,29 @@ { "input": { "arguments": [ + "imap://curl.se:22/", + "-s", + "port=143" + ] + }, + "required": ["imap-options"], + "expected": { + "stdout": "imap://curl.se/\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "--keep-port", "https://curl.se:22/", "-s", "port=443" ] }, "expected": { - "stdout": "https://curl.se/\n", + "stdout": "https://curl.se:443/\n", "stderr": "", "returncode": 0 } @@ -274,6 +290,7 @@ { "input": { "arguments": [ + "--keep-port", "https://curl.se:22/", "-s", "port=443", @@ -282,7 +299,7 @@ ] }, "expected": { - "stdout": "https://curl.se/\n", + "stdout": "https://curl.se:443/\n", "stderr": "", "returncode": 0 } @@ -307,13 +324,14 @@ "arguments": [ "--default-port", "--url", - "https://curl.se/we/are.html", + "imap://curl.se/we/are.html", "--get", "{port}" ] }, + "required": ["imap-options"], "expected": { - "stdout": "443\n", + "stdout": "143\n", "stderr": "", "returncode": 0 } @@ -372,6 +390,7 @@ "{options}" ] }, + "required": ["imap-options"], "expected": { "stdout": "crazy\n", "stderr": "", @@ -489,13 +508,14 @@ "input": { "arguments": [ "--url", - "https://curl.se/we/are.html", + "imap://curl.se/we/are.html", "-g", "{default:port}" ] }, + "required": ["imap-options"], "expected": { - "stdout": "443\n", + "stdout": "143\n", "stderr": "", "returncode": 0 } @@ -846,11 +866,12 @@ { "input": { "arguments": [ + "--keep-port", "https://hello:443/foo" ] }, "expected": { - "stdout": "https://hello/foo\n", + "stdout": "https://hello:443/foo\n", "stderr": "", "returncode": 0 } @@ -858,11 +879,12 @@ { "input": { "arguments": [ + "--keep-port", "ftp://hello:21/foo" ] }, "expected": { - "stdout": "ftp://hello/foo\n", + "stdout": "ftp://hello:21/foo\n", "stderr": "", "returncode": 0 } @@ -884,13 +906,14 @@ { "input": { "arguments": [ + "--keep-port", "ftp://hello:443/foo", "-s", "scheme=https" ] }, "expected": { - "stdout": "https://hello/foo\n", + "stdout": "https://hello:443/foo\n", "stderr": "", "returncode": 0 } @@ -1191,6 +1214,7 @@ { "input": { "arguments": [ + "--keep-port", "https://curl.se", "--iterate", "port=80 81 443" @@ -1199,7 +1223,7 @@ "expected": { "stderr": "", "returncode": 0, - "stdout": "https://curl.se:80/\nhttps://curl.se:81/\nhttps://curl.se/\n" + "stdout": "https://curl.se:80/\nhttps://curl.se:81/\nhttps://curl.se:443/\n" } }, { @@ -1656,17 +1680,18 @@ { "input": { "arguments": [ - "imaps://user:password;crazy@[ff00::1234%hello]:1234/path?a=b&c=d#fragment", + "imap://user:password;crazy@[ff00::1234%hello]:1234/path?a=b&c=d#fragment", "--json" ] }, + "required": ["imap-options"], "expected": { "returncode": 0, "stdout": [ { - "url": "imaps://user:password;crazy@[ff00::1234%25hello]:1234/path?a=b&c=d#fragment", + "url": "imap://user:password;crazy@[ff00::1234%25hello]:1234/path?a=b&c=d#fragment", "parts": { - "scheme": "imaps", + "scheme": "imap", "user": "user", "password": "password", "options": "crazy", @@ -1694,14 +1719,15 @@ { "input": { "arguments": [ - "http://example.com/", + "imap://example.com/", "--get", "port: {port}, default:port: {default:port}" ] }, + "required": ["imap-options"], "expected": { "returncode": 0, - "stdout": "port: , default:port: 80\n" + "stdout": "port: , default:port: 143\n" } }, { @@ -2014,7 +2040,7 @@ "returncode": 0, "stderr": "" } - }, + }, { "input" : { "arguments": [ @@ -2040,7 +2066,7 @@ } ] } - ] + ] } }, { @@ -2068,7 +2094,7 @@ } ] } - ] + ] } }, { @@ -2096,7 +2122,7 @@ } ] } - ] + ] } }, { @@ -2204,5 +2230,198 @@ "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 + } + }, + { + "input": { + "arguments": [ + "0?00%000000000000000000000=0000000000" + ] + }, + "expected": { + "stdout": "http://0.0.0.0/?00%000000000000000000000=0000000000\n", + "stderr": "", + "returncode": 0 + } + }, + { + "input": { + "arguments": [ + "--json", + "0?0%000000000000000000000000000000000" + ] + }, + "expected": { + "returncode": 0, + "stderr": "", + "stdout": [ + { + "url": "http://0.0.0.0/?0%000000000000000000000000000000000", + "parts": { + "scheme": "http", + "host": "0.0.0.0", + "path": "/" + }, + "params": [ + { + "key": "0\u00000000000000000000000000000000000", + "value": "" + } + ] + } + ] + } + }, + { + "input": { + "arguments": [ + "--json", + "0?0%000000000000000000000000000000000=000%0000000000" + ] + }, + "expected": { + "returncode": 0, + "stderr": "", + "stdout": [ + { + "url": "http://0.0.0.0/?0%000000000000000000000000000000000=000%0000000000", + "parts": { + "scheme": "http", + "host": "0.0.0.0", + "path": "/" + }, + "params": [ + { + "key": "0\u00000000000000000000000000000000000", + "value": "000\u000000000000" + } + ] + } + ] + } + } ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/trurl.1 new/trurl-trurl-0.10/trurl.1 --- old/trurl-trurl-0.9/trurl.1 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/trurl.1 2024-02-19 23:14:18.000000000 +0100 @@ -196,6 +196,15 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/trurl.c new/trurl-trurl-0.10/trurl.c --- old/trurl-trurl-0.9/trurl.c 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/trurl.c 2024-02-19 23:14:18.000000000 +0100 @@ -26,11 +26,21 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <stdbool.h> #include <curl/curl.h> #include <curl/mprintf.h> #include <stdint.h> +#if defined(_MSC_VER) && (_MSC_VER < 1800) +typedef enum { + bool_false = 0, + bool_true = 1 +} bool; +#define false bool_false +#define true bool_true +#else +#include <stdbool.h> +#endif + #include <locale.h> /* for setlocale() */ #include "version.h" @@ -61,6 +71,9 @@ #if CURL_AT_LEAST_VERSION(8,3,0) #define SUPPORTS_PUNY2IDN #endif +#if CURL_AT_LEAST_VERSION(7,30,0) +#define SUPPORTS_IMAP_OPTIONS +#endif #define OUTPUT_URL 0 /* default */ #define OUTPUT_SCHEME 1 @@ -126,6 +139,7 @@ #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 */ @@ -137,28 +151,27 @@ } #endif -static void warnf(char *fmt, ...) +static void message_low(const char *prefix, const char *suffix, + const char *fmt, va_list ap) +{ + fputs(prefix, stderr); + vfprintf(stderr, fmt, ap); + fputs(suffix, stderr); +} + +static void warnf_low(const char *fmt, va_list ap) +{ + message_low(WARN_PREFIX, "\n", fmt, ap); +} + +static void warnf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); - fputs(WARN_PREFIX, stderr); - vfprintf(stderr, fmt, ap); - fputs("\n", stderr); + warnf_low(fmt, ap); va_end(ap); } -#define VERIFY(o, exit_code, ...) \ - do { \ - if(!o->verify) \ - warnf(__VA_ARGS__); \ - else { \ - /* make sure to terminate the JSON array */ \ - if(o->jsonout) \ - printf("%s]\n", o->urls ? "\n" : ""); \ - errorf(o, exit_code, __VA_ARGS__); \ - } \ - } while(0) - static void help(void) { int i; @@ -179,6 +192,8 @@ " --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" @@ -199,34 +214,46 @@ static void show_version(void) { curl_version_info_data *data = curl_version_info(CURLVERSION_NOW); - fprintf(stdout, "%s version %s libcurl/%s [built-with %s]\n", - PROGNAME, TRURL_VERSION_TXT, data->version, LIBCURL_VERSION); /* puny code isn't guaranteed based on the version, so it must be polled * from libcurl */ - bool supports_puny = false; -#ifdef SUPPORTS_PUNYCODE - const char *const *feature_name = data->feature_names; - while(*feature_name && !supports_puny) { - supports_puny = !strncmp(*feature_name, "IDN", 3); - feature_name++; +#if defined(SUPPORTS_PUNYCODE) || defined(SUPPORTS_PUNY2IDN) + bool supports_puny = (data->features & CURL_VERSION_IDN) != 0; +#endif +#if defined(SUPPORTS_IMAP_OPTIONS) + bool supports_imap = false; + const char *const *protocol_name = data->protocols; + while(*protocol_name && !supports_imap) { + supports_imap = !strncmp(*protocol_name, "imap", 3); + protocol_name++; } #endif - fprintf(stdout, "features: %s", supports_puny?"punycode ":""); + fprintf(stdout, "%s version %s libcurl/%s [built-with %s]\n", + PROGNAME, TRURL_VERSION_TXT, data->version, LIBCURL_VERSION); + fprintf(stdout, "features:"); +#ifdef SUPPORTS_PUNYCODE + if(supports_puny) + fprintf(stdout, " punycode"); +#endif #ifdef SUPPORTS_ALLOW_SPACE - fprintf(stdout, "white-space "); + fprintf(stdout, " white-space"); #endif #ifdef SUPPORTS_ZONEID - fprintf(stdout, "zone-id "); + fprintf(stdout, " zone-id"); #endif #ifdef SUPPORTS_URL_STRERROR - fprintf(stdout, "url-strerror "); + fprintf(stdout, " url-strerror"); #endif #ifdef SUPPORTS_NORM_IPV4 - fprintf(stdout, "normalize-ipv4 "); + fprintf(stdout, " normalize-ipv4"); +#endif +#ifdef SUPPORTS_IMAP_OPTIONS + if(supports_imap) + fprintf(stdout, " imap-options"); #endif #ifdef SUPPORTS_PUNY2IDN - fprintf(stdout, "punycode2idn"); + if(supports_puny) + fprintf(stdout, " punycode2idn"); #endif fprintf(stdout, "\n"); @@ -248,6 +275,7 @@ 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; @@ -266,12 +294,13 @@ bool urlencode; bool end_of_options; bool quiet_warnings; + bool force_replace; /* -- stats -- */ unsigned int urls; }; -void trurl_warnf(struct option *o, char *fmt, ...) +static void trurl_warnf(struct option *o, const char *fmt, ...) { if(!o->quiet_warnings) { va_list ap; @@ -297,22 +326,47 @@ 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); } -static void errorf(struct option *o, int exit_code, char *fmt, ...) +static void errorf_low(const char *fmt, va_list ap) +{ + message_low(ERROR_PREFIX, "\n" + ERROR_PREFIX "Try " PROGNAME " -h for help\n", fmt, ap); +} + +static void errorf(struct option *o, int exit_code, const char *fmt, ...) { va_list ap; va_start(ap, fmt); - fputs(ERROR_PREFIX, stderr); - vfprintf(stderr, fmt, ap); - fputs("\n" ERROR_PREFIX "Try " PROGNAME " -h for help\n", stderr); + errorf_low(fmt, ap); va_end(ap); trurl_cleanup_options(o); curl_global_cleanup(); exit(exit_code); } +static void verify(struct option *o, int exit_code, const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + if(!o->verify) { + warnf_low(fmt, ap); + va_end(ap); + } + else { + /* make sure to terminate the JSON array */ + if(o->jsonout) + printf("%s]\n", o->urls ? "\n" : ""); + errorf_low(fmt, ap); + va_end(ap); + trurl_cleanup_options(o); + curl_global_cleanup(); + exit(exit_code); + } +} + static char *strurldecode(const char *url, int inlength, int *outlength) { return curl_easy_unescape(NULL, inlength ? url : "", inlength, @@ -365,7 +419,7 @@ char *urle; if(p) { /* URL encode the left and the right side of the '=' separately */ - char *f1 = curl_easy_escape(NULL, query, p - query); + char *f1 = curl_easy_escape(NULL, query, (int)(p - query)); char *f2 = curl_easy_escape(NULL, p + 1, 0); urle = curl_maprintf("%s=%s", f1, f2); curl_free(f1); @@ -420,6 +474,18 @@ 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) @@ -536,6 +602,15 @@ 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; @@ -549,11 +624,11 @@ struct string *qp = urldecode ? qpairsdec : qpairs; for(i = 0; i< nqpairs; i++) { - if(!strncmp(key, qp[i].str, klen) && - (qp[i].str[klen] == '=')) { + if(!strncmp(key, qp[i].str, klen) && (qp[i].str[klen] == '=')) { if(shown) fputc(' ', stream); - fputs(&qp[i].str[klen + 1], stream); + fprintf(stream, "%.*s", (int) (qp[i].len - klen - 1), + &qp[i].str[klen + 1]); if(!showall) break; shown = true; @@ -617,7 +692,7 @@ CURLUcode rc = geturlpart(o, modifiers, uh, CURLUPART_URL, &url); if(rc) { trurl_cleanup_options(o); - VERIFY(o, ERROR_BADURL, "invalid url [%s]", curl_url_strerror(rc)); + verify(o, ERROR_BADURL, "invalid url [%s]", curl_url_strerror(rc)); return; } fputs(url, stream); @@ -889,7 +964,7 @@ CURLUcode rc = geturlpart(o, 0, uh, CURLUPART_URL, &url); if(rc) { trurl_cleanup_options(o); - VERIFY(o, ERROR_BADURL, "invalid url [%s]", curl_url_strerror(rc)); + verify(o, ERROR_BADURL, "invalid url [%s]", curl_url_strerror(rc)); return; } printf("%s\n {\n \"url\": ", o->urls ? "," : ""); @@ -914,11 +989,11 @@ int j; fputs(",\n \"params\": [\n", stdout); for(j = 0 ; j < nqpairs; j++) { - const char *sep = strchr(qpairsdec[j].str, '='); + const char *sep = memchr(qpairsdec[j].str, '=', qpairsdec[j].len); const char *value = sep ? sep + 1 : ""; - + int value_len = (int) qpairsdec[j].len - (int)(value - qpairsdec[j].str); /* don't print out empty/trimmed values */ - if(!qpairsdec[j].str[0]) + if(!qpairsdec[j].len || !qpairsdec[j].str[0]) continue; if(!first) fputs(",\n", stdout); @@ -929,7 +1004,7 @@ qpairsdec[j].len, false); fputs(",\n \"value\": ", stdout); - jsonString(stdout, sep?value:"", sep?qpairsdec[j].len:0, false); + jsonString(stdout, sep?value:"", sep?value_len:0, false); fputs("\n }", stdout); } fputs("\n ]", stdout); @@ -942,11 +1017,12 @@ { struct curl_slist *node; for(node = o->trim_list; node; node = node->next) { + char *ptr; char *instr = node->data; if(strncmp(instr, "query", 5)) /* for now we can only trim query components */ errorf(o, ERROR_TRIM, "Unsupported trim component: %s", instr); - char *ptr = strchr(instr, '='); + ptr = strchr(instr, '='); if(ptr && (ptr > instr)) { /* 'ptr' should be a fixed string or a pattern ending with an asterisk */ @@ -992,7 +1068,7 @@ !strncasecmp(q, ptr, inslen))) { /* this qpair should be stripped out */ free(qpairs[i].str); - curl_free(qpairsdec[i].str); + free(qpairsdec[i].str); qpairs[i].str = strdup(""); /* marked as deleted */ qpairs[i].len = 0; qpairsdec[i].str = strdup(""); /* marked as deleted */ @@ -1005,7 +1081,7 @@ } /* memdup the amount and add a trailing zero */ -struct string *memdupzero(char *source, size_t len) +static struct string *memdupzero(char *source, size_t len) { struct string *ret = malloc(sizeof(struct string)); if(!ret) @@ -1022,7 +1098,7 @@ } /* URL decode the pair and return it in an allocated chunk */ -struct string *memdupdec(char *source, size_t len, bool json) +static struct string *memdupdec(char *source, size_t len, bool json) { char *sep = memchr(source, '=', len); char *left = NULL; @@ -1030,14 +1106,14 @@ int right_len = 0; int left_len = 0; char *str; - - left = strurldecode(source, sep ? (size_t)(sep - source) : len, + struct string *ret; + left = strurldecode(source, (int)(sep ? (size_t)(sep - source) : len), &left_len); if(sep) { char *p; int plen; - right = strurldecode(sep + 1, len - (sep - source) - 1, - (int *)&right_len); + right = strurldecode(sep + 1, (int)(len - (sep - source) - 1), + &right_len); /* convert null bytes to periods */ for(plen = right_len, p = right; plen; plen--, p++) { @@ -1046,26 +1122,22 @@ } } } - - str = curl_maprintf("%.*s%s%.*s", left_len, left, - right ? "=":"", - right_len, right?right:""); - /* handle strings with null characters */ - if(sep && right) { - memcpy(str + left_len + 1, right, right_len); + str = malloc(sizeof(char) * (left_len + (sep?(right_len + 1):0))); + if(!str) + return NULL; + memcpy(str, left, left_len); + if(sep) { + str[left_len] = '='; + memcpy(str + 1 + left_len, right, right_len); } curl_free(right); curl_free(left); - struct string *ret = malloc(sizeof(struct string)); + ret = malloc(sizeof(struct string)); if(!ret) { return NULL; } ret->str = str; - if(right) - ret->len = right_len; - else { - ret->len = left_len; - } + ret->len = left_len + (sep?(right_len + 1):0); return ret; } @@ -1077,7 +1149,7 @@ if(qpairs[i].len) { free(qpairs[i].str); qpairs[i].str = NULL; - curl_free(qpairsdec[i].str); + free(qpairsdec[i].str); qpairsdec[i].str = NULL; } } @@ -1162,10 +1234,11 @@ /* sort case insensitively */ static int cmpfunc(const void *p1, const void *p2) { - int len = (((struct string *)p1)->len) < (((struct string *)p2)->len)? - (((struct string *)p1)->len):(((struct string *)p2)->len); + int i; + int len = (int)((((struct string *)p1)->len) < (((struct string *)p2)->len)? + (((struct string *)p1)->len) : (((struct string *)p2)->len)); - for(int i = 0; i < len; i++) { + for(i = 0; i < len; i++) { char c1 = ((struct string *)p1)->str[i] | ('a' - 'A'); char c2 = ((struct string *)p2)->str[i] | ('a' - 'A'); if(c1 != c2) @@ -1184,6 +1257,60 @@ } } +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, @@ -1209,14 +1336,14 @@ CURLUcode rc = seturl(o, uh, url); if(rc) { curl_url_cleanup(uh); - VERIFY(o, ERROR_BADURL, "%s [%s]", curl_url_strerror(rc), url); + verify(o, ERROR_BADURL, "%s [%s]", curl_url_strerror(rc), url); return; } if(o->redirect) { rc = seturl(o, uh, o->redirect); if(rc) { curl_url_cleanup(uh); - VERIFY(o, ERROR_BADURL, "invalid redirection: %s [%s]", + verify(o, ERROR_BADURL, "invalid redirection: %s [%s]", curl_url_strerror(rc), o->redirect); return; } @@ -1333,6 +1460,9 @@ /* 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); @@ -1350,7 +1480,7 @@ if(rc) { if(o->verify) /* only clean up if we're exiting */ curl_url_cleanup(uh); - VERIFY(o, ERROR_URL, "not enough input for a URL"); + verify(o, ERROR_URL, "not enough input for a URL"); url_is_invalid = true; } else { @@ -1358,7 +1488,7 @@ if(rc) { if(o->verify) /* only clean up if we're exiting */ curl_url_cleanup(uh); - VERIFY(o, ERROR_BADURL, "%s [%s]", curl_url_strerror(rc), + verify(o, ERROR_BADURL, "%s [%s]", curl_url_strerror(rc), ourl); url_is_invalid = true; } @@ -1370,7 +1500,7 @@ else { if(o->verify) /* only clean up if we're exiting */ curl_url_cleanup(uh); - VERIFY(o, ERROR_BADURL, "url became invalid"); + verify(o, ERROR_BADURL, "url became invalid"); url_is_invalid = true; } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/trurl-trurl-0.9/version.h new/trurl-trurl-0.10/version.h --- old/trurl-trurl-0.9/version.h 2023-10-31 14:08:47.000000000 +0100 +++ new/trurl-trurl-0.10/version.h 2024-02-19 23:14:18.000000000 +0100 @@ -24,6 +24,6 @@ * ***************************************************************************/ -#define TRURL_VERSION_TXT "0.8" +#define TRURL_VERSION_TXT "0.10" #endif
