svn commit: r1928796 - in apr/apr/trunk: . include strings test

[email protected] Sat, 27 Sep 2025 17:19:11 -0000
Newsgroups gmane.comp.apache.apr.cvs
Message-ID <[email protected]>
Author: minfrin
Date: Sat Sep 27 17:19:11 2025
New Revision: 1928796

Log:
Add apr_strqtok() function to tokenise quoted strings.

Modified:
   apr/apr/trunk/CHANGES
   apr/apr/trunk/include/apr_strings.h
   apr/apr/trunk/strings/apr_strtok.c
   apr/apr/trunk/test/teststr.c

Modified: apr/apr/trunk/CHANGES
==============================================================================
--- apr/apr/trunk/CHANGES	Sat Sep 27 16:59:32 2025	(r1928795)
+++ apr/apr/trunk/CHANGES	Sat Sep 27 17:19:11 2025	(r1928796)
@@ -1,6 +1,9 @@
                                                      -*- coding: utf-8 -*-
 Changes for APR 2.0.0
 
+  *) apr_strings: Add apr_strqtok() function to tokenise quoted strings.
+     [Graham Leggett]
+
   *) apr_uri: Update apr_uri_parse() and apr_uri_unparse() to decode / encode
      a % separating a zone identifier in an IPv6 literal if detected following
      RFC6874. PR69754. [Jens Finkhaeuser <[email protected]>, Ruediger Pluem]

Modified: apr/apr/trunk/include/apr_strings.h
==============================================================================
--- apr/apr/trunk/include/apr_strings.h	Sat Sep 27 16:59:32 2025	(r1928795)
+++ apr/apr/trunk/include/apr_strings.h	Sat Sep 27 17:19:11 2025	(r1928796)
@@ -291,13 +291,13 @@ APR_DECLARE(apr_status_t) apr_tokenize_t
  * argument.
  * @param str The string to separate; this should be specified on the
  *            first call to apr_strtok() for a given string, and NULL
- *            on subsequent calls.
+ *            on subsequent calls. This string is modified in place.
  * @param sep The set of delimiters
  * @param last State saved by apr_strtok() between calls.
  * @return The next token from the string
  * @note the 'last' state points to the trailing NUL char of the final
  * token, otherwise it points to the character following the current
- * token (all successive or empty occurances of sep are skiped on the
+ * token (all successive or empty occurances of sep are skipped on the
  * subsequent call to apr_strtok).  Therefore it is possible to avoid
  * a strlen() determination, with the following logic;
  * toklen = last - retval; if (*last) --toklen;
@@ -305,6 +305,27 @@ APR_DECLARE(apr_status_t) apr_tokenize_t
 APR_DECLARE(char *) apr_strtok(char *str, const char *sep, char **last);
 
 /**
+ * Split a string into separate null-terminated possibly quoted tokens.
+ * The tokens are delimited in the string by one or more characters
+ * from the sep argument. A quoted token may be separated by single or
+ * double quotes, and quoted sections may appear more than once in each
+ * token. The backslash character escapes each quote. The apr_strqtok
+ * function can be used interchangeably with the apr_strtok function
+ * using the same state variable.
+ * @param str The string to separate; this should be specified on the
+ *            first call to apr_strtok() for a given string, and NULL 
+ *            on subsequent calls. This string is modified in place.
+ * @param sep The set of delimiters
+ * @param last State saved by apr_strqtok() between calls.
+ * @return The next token from the string
+ * @note while the 'last' state points to the trailing NUL char of the
+ * final token, otherwise it points to the character following the
+ * current token, no string length can be inferred as quoted characters
+ * and backslash escape characters are removed from the final token.
+ */
+APR_DECLARE(char *) apr_strqtok(char *str, const char *sep, char **last);
+
+/**
  * @defgroup APR_Strings_Snprintf snprintf implementations
  * @warning
  * These are snprintf implementations based on apr_vformatter().

Modified: apr/apr/trunk/strings/apr_strtok.c
==============================================================================
--- apr/apr/trunk/strings/apr_strtok.c	Sat Sep 27 16:59:32 2025	(r1928795)
+++ apr/apr/trunk/strings/apr_strtok.c	Sat Sep 27 17:19:11 2025	(r1928796)
@@ -54,3 +54,85 @@ APR_DECLARE(char *) apr_strtok(char *str
 
     return token;
 }
+
+APR_DECLARE(char *) apr_strqtok(char *str, const char *sep, char **last)
+{
+    char *token;
+    apr_size_t rewind = 0;
+    char c, q = 0, s = 0;
+
+    if (!str) {         /* subsequent call */
+        str = *last;    /* start where we left off */
+    }
+
+    /* skip characters in sep (will terminate at '\0') */
+    while (*str && strchr(sep, *str)) {
+        ++str;
+    }
+
+    if (!*str) {        /* no more tokens */
+        return NULL;
+    }
+
+    token = str;
+
+    /* skip quoted sections */
+    while ((c = *str)) {
+
+        if (!q) {
+            if ('\'' == c) {
+                q = '\'';
+                rewind++;
+            }
+            else if ('\"' == c) {
+                q = '\"';
+                rewind++;
+            }
+            else if (strchr(sep, c)) {
+                break;
+            }
+            else if (rewind) {
+                str[-rewind] = c;
+            }
+        }
+        else {
+            if (!s) {
+                if ('\\' == c) {
+                    s = c;
+                    rewind++;
+                }
+                else if (!s && q == c) {
+                    rewind++;
+                    q = 0;
+                }
+                else if (rewind) {
+                    str[-rewind] = c;
+                }
+            }
+            else {
+                s = 0;
+                if (rewind) {
+                    str[-rewind] = c;
+                }
+            }
+        }
+
+        str++;
+    }
+
+    if (rewind) {
+        str[-rewind] = '\0';
+    }
+
+    /* prepare for the next call (will terminate at '\0)
+     */
+    *last = str;
+
+    if (**last) {
+        **last = '\0';
+        ++*last;
+    }
+
+    return token;
+}
+

Modified: apr/apr/trunk/test/teststr.c
==============================================================================
--- apr/apr/trunk/test/teststr.c	Sat Sep 27 16:59:32 2025	(r1928795)
+++ apr/apr/trunk/test/teststr.c	Sat Sep 27 17:19:11 2025	(r1928796)
@@ -94,6 +94,100 @@ static void test_strtok(abts_case *tc, v
     }
 }
 
+static void test_strqtok(abts_case *tc, void *data)
+{
+    char *retval1, *retval2;
+    char *str1, *str2;
+    char *state1, *state2;
+
+    /* test empty string */
+    str1 = str2 = "";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, ",", &state1);
+    retval2 = apr_strqtok(str2, ",", &state2);
+
+    ABTS_TRUE(tc, retval1 == NULL);
+    ABTS_TRUE(tc, retval2 == NULL);
+
+    /* test delimiters only */
+    str1 = str2 = ",";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, ",", &state1);
+    retval2 = apr_strqtok(str2, ",", &state2);
+
+    /* test unquoted string */
+    str1 = str2 = "key";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, "=", &state1);
+    retval2 = apr_strqtok(str2, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "key");
+    ABTS_STR_EQUAL(tc, retval2, "key");
+
+    /* test quoted string */
+    str1 = str2 = "\"key\"";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, "=", &state1);
+    retval2 = apr_strqtok(str2, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "\"key\"");
+    ABTS_STR_EQUAL(tc, retval2, "key");
+
+    /* test quoted key value pair */
+    str1 = str2 = "\"key\"='value'";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, "=", &state1);
+    retval2 = apr_strqtok(str2, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "\"key\"");
+    ABTS_STR_EQUAL(tc, retval2, "key");
+
+    retval1 = apr_strtok(NULL, "=", &state1);
+    retval2 = apr_strqtok(NULL, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "'value'");
+    ABTS_STR_EQUAL(tc, retval2, "value");
+
+    retval1 = apr_strtok(NULL, "=", &state1);
+    retval2 = apr_strqtok(NULL, "=", &state2);
+
+    ABTS_TRUE(tc, retval1 == NULL);
+    ABTS_TRUE(tc, retval2 == NULL);
+
+    /* test quoted against quoted */
+    str1 = str2 = "\"k\"'ey'";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, "=", &state1);
+    retval2 = apr_strqtok(str2, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "\"k\"'ey'");
+    ABTS_STR_EQUAL(tc, retval2, "key");
+
+    /* test escapes, unquoted against quoted */
+    str1 = str2 = "outside\\'in\\'sid=e'";
+    str1 = apr_pstrdup(p, str1);
+    str2 = apr_pstrdup(p, str2);
+
+    retval1 = apr_strtok(str1, "=", &state1);
+    retval2 = apr_strqtok(str2, "=", &state2);
+
+    ABTS_STR_EQUAL(tc, retval1, "outside\\'in\\'sid");
+    ABTS_STR_EQUAL(tc, retval2, "outside\\in'sid=e");
+
+}
+
 static void snprintf_noNULL(abts_case *tc, void *data)
 {
     char buff[100];
@@ -512,6 +606,7 @@ abts_suite *teststr(abts_suite *suite)
     abts_run_test(suite, snprintf_noNULL, NULL);
     abts_run_test(suite, snprintf_underflow, NULL);
     abts_run_test(suite, test_strtok, NULL);
+    abts_run_test(suite, test_strqtok, NULL);
     abts_run_test(suite, string_error, NULL);
     abts_run_test(suite, string_long, NULL);
     abts_run_test(suite, string_strtoi64, NULL);