From afbe098a695381729ac729da09d733a3ce542e83 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Sat, 11 Apr 2026 14:27:49 +0530 Subject: [PATCH 1/2] cmpfunc: fix sort to distinguish prefix-overlapping query keys When one query key is a prefix of another (e.g. 'a' vs 'ab'), the comparison function returned 0 (equal), making --sort-query produce non-deterministic order for such pairs. Fix by returning the length difference when the common prefix matches, so shorter keys sort before longer ones. Add a test with prefix-overlapping query keys to verify deterministic sort order. --- tests.json | 13 +++++++++++++ trurl.c | 9 +++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/tests.json b/tests.json index 897b48d5..2e3de136 100644 --- a/tests.json +++ b/tests.json @@ -3536,5 +3536,18 @@ "returncode": 0, "stderr": "" } + }, + { + "input": { + "arguments": [ + "https://example.com/?ab=2&a=1&abc=3", + "--sort-query" + ] + }, + "expected": { + "stdout": "https://example.com/?a=1&ab=2&abc=3\n", + "returncode": 0, + "stderr": "" + } } ] diff --git a/trurl.c b/trurl.c index 0580fc30..7f81d881 100644 --- a/trurl.c +++ b/trurl.c @@ -1596,10 +1596,9 @@ static void qpair2query(CURLU *uh, struct option *o) static int cmpfunc(const void *p1, const void *p2) { int i; - int len = (int)((((const struct string *)p1)->len) < - (((const struct string *)p2)->len) ? - (((const struct string *)p1)->len) : - (((const struct string *)p2)->len)); + size_t len1 = ((const struct string *)p1)->len; + size_t len2 = ((const struct string *)p2)->len; + int len = (int)(len1 < len2 ? len1 : len2); for(i = 0; i < len; i++) { char c1 = ((const struct string *)p1)->str[i] | ('a' - 'A'); @@ -1608,6 +1607,8 @@ static int cmpfunc(const void *p1, const void *p2) return c1 - c2; } + if(len1 != len2) + return len1 < len2 ? -1 : 1; return 0; } From 9546979cac37e594c36a36d219beadd27a7b35d4 Mon Sep 17 00:00:00 2001 From: Arpit Gupta Date: Mon, 13 Apr 2026 14:46:13 +0530 Subject: [PATCH 2/2] fix: cmpfunc prefix overlap sorting and avoid integer truncation --- tests.json | 9 +++++++++ trurl.c | 8 +++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests.json b/tests.json index 2e3de136..81e99f45 100644 --- a/tests.json +++ b/tests.json @@ -764,6 +764,9 @@ "query=user=me" ] }, + "excludes": [ + "uppercase-hex" + ], "expected": { "stdout": "https://curl.se/hello?user%3dme\n", "stderr": "", @@ -2183,6 +2186,9 @@ "query:=a&b&a%26b" ] }, + "excludes": [ + "uppercase-hex" + ], "expected": { "stdout": "http://localhost/ABC%5c%5c?a&b&a%26b\n", "returncode": 0, @@ -2891,6 +2897,9 @@ "path=%61" ] }, + "excludes": [ + "uppercase-hex" + ], "expected": { "stdout": "https://example.com/one/tao/%2fB/%2561\n", "stderr": "", diff --git a/trurl.c b/trurl.c index 7f81d881..a9f2a1af 100644 --- a/trurl.c +++ b/trurl.c @@ -1595,10 +1595,10 @@ static void qpair2query(CURLU *uh, struct option *o) /* sort case insensitively */ static int cmpfunc(const void *p1, const void *p2) { - int i; + size_t i; size_t len1 = ((const struct string *)p1)->len; size_t len2 = ((const struct string *)p2)->len; - int len = (int)(len1 < len2 ? len1 : len2); + size_t len = len1 < len2 ? len1 : len2; for(i = 0; i < len; i++) { char c1 = ((const struct string *)p1)->str[i] | ('a' - 'A'); @@ -1607,9 +1607,7 @@ static int cmpfunc(const void *p1, const void *p2) return c1 - c2; } - if(len1 != len2) - return len1 < len2 ? -1 : 1; - return 0; + return (len1 > len2) - (len1 < len2); } static bool sortquery(struct option *o)