#include "str.h"
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
const struct error_list str_errors = ERROR_LIST("str",
ERROR_TYPE( ERR_STR_FMT_TAG, "invalid parameter tag syntax" ),
ERROR_TYPE( ERR_STR_FMT_NAME_LEN, "invalid parameter name length" ),
ERROR_TYPE( ERR_STR_FMT_NAME, "invalid/unknown parameter name" ),
ERROR_TYPE( ERR_STR_FMT_FLAGS_LEN, "invalid paramter flags length" ),
ERROR_TYPE( ERR_STR_FMT_FLAG, "invalid paramter flag" ),
ERROR_TYPE( ERR_STR_FMT_BUF_LEN, "output buffer ran out" )
);
size_t str_append_num (char *buf, size_t buf_size, const char *str, ssize_t len)
{
size_t str_len;
// copy the bytes
// len will either be >0 or <0
for (str_len = 0; *str && buf_size > 1 && len != 0; str_len++, buf_size--, len--)
*buf++ = *str++;
// count the rest
for (; *str && len != 0; str++, len--)
str_len++;
if (buf_size)
// NUL-terminate
*buf = '\0';
// ok
return str_len;
}
size_t str_append (char *buf, size_t buf_size, const char *str)
{
return str_append_num(buf, buf_size, str, -1);
}
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;
}
size_t str_append_fmt_va (char *buf, size_t buf_size, const char *fmt, va_list vargs)
{
int ret;
// do the formatting
ret = vsnprintf(buf, buf_size, fmt, vargs);
// XXX: not all vsnprintf implementations do this
assert(ret >= 0);
// ok
return ret;
}
size_t str_append_fmt (char *buf, size_t buf_size, const char *fmt, ...)
{
va_list vargs;
int ret;
va_start(vargs, fmt);
ret = str_append_fmt_va(buf, buf_size, fmt, vargs);
va_end(vargs);
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 }
};
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;
}
/**
* Number of bytes reserved by str_quote for the trailing overflow stuff
*/
#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;
}
/**
* Output the data for a single parameter
*/
static err_t str_format_param (char **buf, size_t *buf_size, const char *name, const char *flags, str_format_cb func, void *arg, error_t *err)
{
const char *value;
ssize_t value_len = -1;
char flag;
bool use_quote = false;
// look it up
if (func(name, &value, &value_len, arg, err))
return PUSH_ERROR(err, &str_errors, ERR_STR_FMT_VALUE);
// not found?
if (!value)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_NAME);
// parse flags
while ((flag = *flags++)) {
switch (flag) {
case 'r':
// quote
use_quote = true;
break;
default:
// unknown flag
return SET_ERROR(err, &str_errors, ERR_STR_FMT_FLAG);
}
}
// output value
if (use_quote)
*buf += str_advance(NULL, buf_size, str_quote(*buf, *buf_size, value, value_len));
else
*buf += str_advance(NULL, buf_size, str_append_num(*buf, *buf_size, value, value_len));
// ok
return SUCCESS;
}
err_t str_format (char *buf, size_t buf_size, const char *format, str_format_cb func, void *arg, error_t *err)
{
char name_buf[STR_FORMAT_PARAM_MAX + 1], *name_ptr;
size_t name_size;
char flags_buf[STR_FORMAT_FLAGS_MAX + 1], *flags_ptr;
size_t flags_size;
bool in_param = false, in_param_flags = false;
// iterate over the format string
do {
// check buffer state
if (!buf_size)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_BUF_LEN);
// inspect this char
switch (*format) {
case '{':
// syntax
if (in_param)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG);
// init state
in_param = true;
name_ptr = name_buf;
name_size = sizeof(name_buf);
flags_ptr = flags_buf;
flags_size = sizeof(flags_buf);
*name_ptr = *flags_ptr = '\0';
break;
case '}':
// syntax
if (!in_param)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG);
// reset state
in_param = false;
in_param_flags = false;
// output token
if (str_format_param(&buf, &buf_size, name_buf, flags_buf, func, arg, err))
return error_code(err);
break;
case ':':
if (in_param) {
// set state
in_param_flags = true;
break;
}
/* Fallthrough */
default:
if (in_param && in_param_flags ) {
// add to param flags
flags_ptr += str_advance(NULL, &flags_size, str_append_char(flags_ptr, flags_size, *format));
if (!flags_size)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_FLAGS_LEN);
} else if (in_param) {
// add to param name
name_ptr += str_advance(NULL, &name_size, str_append_char(name_ptr, name_size, *format));
if (!name_size)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_NAME_LEN);
} else {
// add to output
buf += str_advance(NULL, &buf_size, str_append_char(buf, buf_size, *format));
}
break;
}
} while (*format++);
// syntax
if (in_param)
return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG);
// ok
return SUCCESS;
}