move test.c dump_str implemention into str.h str_quote, and improve it
authorTero Marttila <terom@fixme.fi>
Sat, 11 Apr 2009 04:25:59 +0300
changeset 125 5c70fb2d6793
parent 124 f18d69425c4f
child 126 8065a624ba37
move test.c dump_str implemention into str.h str_quote, and improve it
src/str.c
src/str.h
src/test.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/str.c	Sat Apr 11 04:25:59 2009 +0300
@@ -0,0 +1,175 @@
+#include "str.h"
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <assert.h>
+
+/**
+ * Writes the given string into the given buffer, writing at most buf_size bytes, including the terminating NUL.
+ *
+ * Returns the number of input bytes that would have been written, not including the terminating NUL.
+ */
+static size_t str_append (char *buf, size_t buf_size, const char *str)
+{
+    size_t str_len;
+    
+    // copy the bytes
+    for (str_len = 0; *str && buf_size > 1; str_len++, buf_size--)
+        *buf++ = *str++;
+    
+    // count the rest
+    for (; *str; str++)
+        str_len++;
+    
+    if (buf_size)
+        // NUL-terminate
+        *buf = '\0';
+
+    // ok
+    return str_len;
+}
+
+/**
+ * Like str_append, but only a single char.
+ *
+ * @see str_append()
+ */
+static size_t str_append_char (char *buf, size_t buf_size, char c)
+{
+    if (buf_size > 1)
+        *buf++ = c;
+
+    if (buf_size > 0)
+        *buf = '\0';
+    
+    return 1;
+}
+
+/**
+ * Like str_append, but using formatted input instead.
+ *
+ * @see str_append()
+ */
+static size_t str_append_fmt (char *buf, size_t buf_size, const char *fmt, ...)
+{
+    va_list vargs;
+    int ret;
+
+    va_start(vargs, fmt);
+
+    // do the formatting
+    ret = vsnprintf(buf, buf_size, fmt, vargs);
+
+    va_end(vargs);
+
+    // XXX: not all vsnprintf implementations do this
+    assert(ret >= 0);
+
+    // ok
+    return ret;
+}
+
+static const struct str_quote_item {
+    /** The char value to quote */
+    char c;
+    
+    /** The quoted value */
+    const char *out;
+} _quote_table[] = {
+    { '\'', "\\'"   },
+    { '\\', "\\\\"  },
+    { '\0', "\\0"   },
+    { '\r', "\\r"   },
+    { '\n', "\\n"   },
+    { 0,    NULL    }
+};
+
+/**
+ * Use str_append* to write out the quoted char value to the given buffer.
+ */
+static size_t str_quote_char (char *buf, size_t buf_size, char c)
+{
+    const struct str_quote_item *quote_item = _quote_table;
+
+    // special quote?
+    for (quote_item = _quote_table; quote_item->c || quote_item->out; quote_item++) {
+        if (quote_item->c == c)
+            return str_append(buf, buf_size, quote_item->out);
+    }
+    
+    // output directly?
+    if (isprint(c))
+        return str_append_char(buf, buf_size, c); 
+    
+    else 
+        return str_append_fmt(buf, buf_size, "\\x%02x", c);
+}
+
+size_t str_advance (size_t *data_size, size_t *buf_size, size_t len)
+{
+    if (data_size)
+        *data_size += len;
+
+    if (len > *buf_size)
+        *buf_size = 0;
+
+    else
+        *buf_size -= len;
+
+    return len;
+}
+
+#define STR_QUOTE_RESERVED 5
+
+size_t str_quote (char *buf, size_t buf_size, const char *str, ssize_t str_len)
+{
+    size_t data_size = 0;
+
+    // NULL?
+    if (str == NULL) {
+        return str_append(buf, buf_size, "NULL");
+
+    } else {
+        // calc length?
+        if (str_len < 0)
+            str_len = strlen(str);
+
+        // quote
+        buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\''));
+    
+        // dump each char
+        while (str_len--) { 
+            if (buf_size > STR_QUOTE_RESERVED) {
+                size_t char_size;
+
+                // output and count the chars
+                char_size = str_quote_char(buf, buf_size, *str++);
+                
+                // if there's still enough room left, commit this and continue
+                if (buf_size > char_size && buf_size - char_size >= STR_QUOTE_RESERVED)
+                    buf += str_advance(&data_size, &buf_size, char_size);
+                
+                else
+                    // keep the buf pointer intact, we'll overwrite it now
+                    data_size += char_size;
+
+            } else {
+                // continue counting the chars, but don't output anything anymore
+                data_size += str_quote_char(NULL, 0, *str++);
+            }
+        }
+
+        // end quote
+        buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\''));
+    }        
+
+    // overflow -> mark
+    if (buf_size < STR_QUOTE_RESERVED)
+        buf += str_advance(NULL, &buf_size, str_append(buf, buf_size, "..."));
+
+    // the total outputted chars
+    return data_size;
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/str.h	Sat Apr 11 04:25:59 2009 +0300
@@ -0,0 +1,29 @@
+#ifndef STR_H
+#define STR_H
+
+/**
+ * @file
+ *
+ * Miscellaneous string utility functions
+ */
+#include <sys/types.h>
+
+/**
+ * Copy the given \a str into \buf, surrounding it with quotes and escaping any data inside.
+ *
+ * At most \a str_len bytes of input will be read, unless given as -1, whereupon str will be read up to the first \0 byte.
+ *
+ * At most \a buf_size bytes of output will be written, if the output string was truncated, it will end in '...', and a
+ * value larger than \a buf_size will be returned.
+ *
+ * As a special case, if \a str is NULL, only the string "NULL" will be output.
+ *
+ * @param buf the buffer to write the output to
+ * @param buf_size the size of the given output buffer
+ * @param str the input string
+ * @param str_len number of bytes of input to process, or -1 to use strlen()
+ * @return the total number of bytes that would have been written out, may be more than buf_size
+ */
+size_t str_quote (char *buf_ptr, size_t buf_size, const char *str, ssize_t str_len);
+
+#endif
--- a/src/test.c	Sat Apr 11 01:54:33 2009 +0300
+++ b/src/test.c	Sat Apr 11 04:25:59 2009 +0300
@@ -7,6 +7,7 @@
 #include "irc_conn.h"
 #include "irc_net.h"
 #include "log.h"
+#include "str.h"
 #include "error.h"
 
 #include <stdlib.h>
@@ -28,13 +29,6 @@
 
 } _test_ctx;
 
-char *dump_str_append (char *buf, const char *str)
-{
-    while (*str)
-        *buf++ = *str++;
-
-    return buf;
-}
 
 /**
  * This re-formats the given string to escape values, and returns a pointer to an internal static buffer.
@@ -55,78 +49,16 @@
     static size_t dump_idx = 0;
     
     // pick a buffer to use
-    const char *str_ptr = str;
-    char *buf_ptr = dump_buf[dump_idx++], *buf = buf_ptr;
+    char *buf = dump_buf[dump_idx++];
     
     // cycle
     if (dump_idx >= DUMP_STR_COUNT)
         dump_idx = 0;
-
-    // NULL?
-    if (str == NULL) {
-        buf = dump_str_append(buf, "NULL");
-        *buf = '\0';
-
-        return buf_ptr;
-    }
-
-    // quote
-    *buf++ = '\'';
     
-    // dump each char
-    for (; (
-            // ...don't consume more than len bytes of str, unless len < 0
-                (len < 0 || (size_t) (str - str_ptr) < (size_t) len)
-
-            // ...stop on NUL
-            &&  *str 
-
-            // ...leave DUMP_STR_TAIL bytes room at the end of buf
-            &&  (size_t) (buf - buf_ptr) < DUMP_STR_BUF - DUMP_STR_TAIL 
-            
-        ); str++
-    ) {
-        if (*str == '\'') {
-            // escape quotes
-            buf = dump_str_append(buf, "\\'");
-
-        } else if (*str == '\\') {
-            // escape escapes
-            buf = dump_str_append(buf, "\\\\");
-
-        } else if (isprint(*str)) {
-            // normal char
-            *buf++ = *str;
-        
-        } else {
-            // something more special
-            switch (*str) {
-                case '\r':
-                    buf = dump_str_append(buf, "\\r"); break;
-
-                case '\n':
-                    buf = dump_str_append(buf, "\\n"); break;
-
-                default:
-                    // format as "\xFF"
-                    buf += snprintf(buf, (DUMP_STR_BUF - (buf - buf_ptr)), "\\x%02x", *str);
-                    break;
-            }
-        }
-    }
-
-    // end quote
-    *buf++ = '\'';
-
-    // overflow?
-    if ((size_t)(buf - buf_ptr) == DUMP_STR_BUF - DUMP_STR_TAIL)
-        buf = dump_str_append(buf, "...");
-
-    // terminate
-    *buf = '\0';
+    str_quote(buf, DUMP_STR_BUF, str, len);
 
     // ok
-    return buf_ptr;
+    return buf;
 }
 
 const char *dump_str (const char *str) 
@@ -292,6 +224,32 @@
     return sock;
 }
 
+void assert_str_quote (size_t buf_size, const char *data, ssize_t len, const char *target, size_t out)
+{
+    char buf[buf_size];
+    
+    size_t ret = str_quote(buf, buf_size, data, len);
+
+    log_debug("str_quote(%zu, %zd) -> %s:%zu / %s:%zu", buf_size, len, buf, ret, target, out);
+
+    assert_strcmp(buf, target);
+    assert(ret == out);
+}
+
+void test_str_quote (void)
+{
+    log_info("testing str_quote()");
+
+    assert_str_quote(5,     NULL,           -1,     "NULL",         4   );
+    assert_str_quote(16,    "foo",          -1,     "'foo'",        5   );
+    assert_str_quote(16,    "foobar",       3,      "'foo'",        5   );
+    assert_str_quote(16,    "\r\n",         -1,     "'\\r\\n'",     6   );
+    assert_str_quote(16,    "\x13",         -1,     "'\\x13'",      6   );
+    assert_str_quote(16,    "x'y",          -1,     "'x\\'y'",      6   );
+    assert_str_quote(7,     "1234567890",   -1,     "'1'...",       12  );
+    assert_str_quote(9,     "1234567890",   -1,     "'123'...",     12  );
+}
+
 void test_dump_str (void)
 {
     log_info("dumping example strings on stdout:");
@@ -1207,25 +1165,26 @@
 #define DEF_TEST_END { NULL, NULL, false }
 
 static struct test _tests[] = {
-    DEF_TEST(            dump_str                   ),
-    DEF_TEST(            sock_test                  ),
-    DEF_TEST(            line_proto                 ),
-    DEF_TEST(            irc_queue                  ),
+    DEF_TEST(           str_quote                   ),
+    DEF_TEST(           dump_str                    ),
+    DEF_TEST(           sock_test                   ),
+    DEF_TEST(           line_proto                  ),
+    DEF_TEST(           irc_queue                   ),
     // XXX: irc_line_parse_invalid_prefix
-    DEF_TEST(            irc_conn                   ),
-    DEF_TEST(            irc_conn_self_nick         ),
-    DEF_TEST(            irc_net                    ),
-    DEF_TEST(            irc_chan_add_offline       ),
-    DEF_TEST(            irc_chan_namreply          ),
-    DEF_TEST(            irc_chan_user_join         ),
-    DEF_TEST(            irc_chan_user_part         ),
-    DEF_TEST(            irc_chan_user_kick         ),
-    DEF_TEST(            irc_chan_self_kick         ),
-    DEF_TEST(            irc_chan_user_nick         ),
-    DEF_TEST(            irc_chan_user_quit         ),
-    DEF_TEST(            irc_chan_CTCP_ACTION       ),
-    DEF_TEST(            irc_chan_privmsg           ),
-    DEF_TEST_OPTIONAL(   fifo                       ),
+    DEF_TEST(           irc_conn                    ),
+    DEF_TEST(           irc_conn_self_nick          ),
+    DEF_TEST(           irc_net                     ),
+    DEF_TEST(           irc_chan_add_offline        ),
+    DEF_TEST(           irc_chan_namreply           ),
+    DEF_TEST(           irc_chan_user_join          ),
+    DEF_TEST(           irc_chan_user_part          ),
+    DEF_TEST(           irc_chan_user_kick          ),
+    DEF_TEST(           irc_chan_self_kick          ),
+    DEF_TEST(           irc_chan_user_nick          ),
+    DEF_TEST(           irc_chan_user_quit          ),
+    DEF_TEST(           irc_chan_CTCP_ACTION        ),
+    DEF_TEST(           irc_chan_privmsg            ),
+    DEF_TEST_OPTIONAL(  fifo                        ),
     DEF_TEST_END
 };