--- /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
};