terom@125: #include "str.h" terom@125: terom@125: #include terom@125: #include terom@125: #include terom@129: #include terom@129: terom@125: #include terom@125: terom@216: const struct error_list str_errors = ERROR_LIST("str", terom@216: ERROR_TYPE( ERR_STR_FMT_TAG, "invalid parameter tag syntax" ), terom@216: ERROR_TYPE( ERR_STR_FMT_NAME_LEN, "invalid parameter name length" ), terom@216: ERROR_TYPE( ERR_STR_FMT_NAME, "invalid/unknown parameter name" ), terom@216: ERROR_TYPE( ERR_STR_FMT_FLAGS_LEN, "invalid paramter flags length" ), terom@216: ERROR_TYPE( ERR_STR_FMT_FLAG, "invalid paramter flag" ), terom@216: ERROR_TYPE( ERR_STR_FMT_BUF_LEN, "output buffer ran out" ) terom@216: ); terom@216: terom@129: size_t str_append_num (char *buf, size_t buf_size, const char *str, ssize_t len) terom@125: { terom@125: size_t str_len; terom@125: terom@125: // copy the bytes terom@129: // len will either be >0 or <0 terom@129: for (str_len = 0; *str && buf_size > 1 && len != 0; str_len++, buf_size--, len--) terom@125: *buf++ = *str++; terom@125: terom@125: // count the rest terom@129: for (; *str && len != 0; str++, len--) terom@125: str_len++; terom@125: terom@125: if (buf_size) terom@125: // NUL-terminate terom@125: *buf = '\0'; terom@125: terom@125: // ok terom@125: return str_len; terom@129: terom@129: } terom@129: terom@129: size_t str_append (char *buf, size_t buf_size, const char *str) terom@129: { terom@129: return str_append_num(buf, buf_size, str, -1); terom@125: } terom@125: terom@128: size_t str_append_char (char *buf, size_t buf_size, char c) terom@125: { terom@125: if (buf_size > 1) terom@125: *buf++ = c; terom@125: terom@125: if (buf_size > 0) terom@125: *buf = '\0'; terom@125: terom@125: return 1; terom@125: } terom@125: terom@133: size_t str_append_fmt_va (char *buf, size_t buf_size, const char *fmt, va_list vargs) terom@133: { terom@133: int ret; terom@133: terom@133: // do the formatting terom@133: ret = vsnprintf(buf, buf_size, fmt, vargs); terom@133: terom@133: // XXX: not all vsnprintf implementations do this terom@133: assert(ret >= 0); terom@133: terom@133: // ok terom@133: return ret; terom@133: } terom@133: terom@128: size_t str_append_fmt (char *buf, size_t buf_size, const char *fmt, ...) terom@125: { terom@125: va_list vargs; terom@125: int ret; terom@125: terom@125: va_start(vargs, fmt); terom@133: ret = str_append_fmt_va(buf, buf_size, fmt, vargs); terom@125: va_end(vargs); terom@125: terom@125: return ret; terom@125: } terom@125: terom@125: static const struct str_quote_item { terom@125: /** The char value to quote */ terom@125: char c; terom@125: terom@125: /** The quoted value */ terom@125: const char *out; terom@125: } _quote_table[] = { terom@125: { '\'', "\\'" }, terom@125: { '\\', "\\\\" }, terom@125: { '\0', "\\0" }, terom@125: { '\r', "\\r" }, terom@125: { '\n', "\\n" }, terom@125: { 0, NULL } terom@125: }; terom@125: terom@128: size_t str_quote_char (char *buf, size_t buf_size, char c) terom@125: { terom@125: const struct str_quote_item *quote_item = _quote_table; terom@125: terom@125: // special quote? terom@125: for (quote_item = _quote_table; quote_item->c || quote_item->out; quote_item++) { terom@125: if (quote_item->c == c) terom@125: return str_append(buf, buf_size, quote_item->out); terom@125: } terom@125: terom@125: // output directly? terom@125: if (isprint(c)) terom@125: return str_append_char(buf, buf_size, c); terom@125: terom@125: else terom@125: return str_append_fmt(buf, buf_size, "\\x%02x", c); terom@125: } terom@125: terom@125: size_t str_advance (size_t *data_size, size_t *buf_size, size_t len) terom@125: { terom@125: if (data_size) terom@125: *data_size += len; terom@125: terom@125: if (len > *buf_size) terom@125: *buf_size = 0; terom@125: terom@125: else terom@125: *buf_size -= len; terom@125: terom@125: return len; terom@125: } terom@125: terom@128: /** terom@128: * Number of bytes reserved by str_quote for the trailing overflow stuff terom@128: */ terom@125: #define STR_QUOTE_RESERVED 5 terom@125: terom@125: size_t str_quote (char *buf, size_t buf_size, const char *str, ssize_t str_len) terom@125: { terom@125: size_t data_size = 0; terom@125: terom@125: // NULL? terom@125: if (str == NULL) { terom@125: return str_append(buf, buf_size, "NULL"); terom@125: terom@125: } else { terom@125: // calc length? terom@125: if (str_len < 0) terom@125: str_len = strlen(str); terom@125: terom@125: // quote terom@125: buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\'')); terom@125: terom@125: // dump each char terom@125: while (str_len--) { terom@125: if (buf_size > STR_QUOTE_RESERVED) { terom@125: size_t char_size; terom@125: terom@125: // output and count the chars terom@125: char_size = str_quote_char(buf, buf_size, *str++); terom@125: terom@125: // if there's still enough room left, commit this and continue terom@125: if (buf_size > char_size && buf_size - char_size >= STR_QUOTE_RESERVED) terom@125: buf += str_advance(&data_size, &buf_size, char_size); terom@125: terom@125: else terom@125: // keep the buf pointer intact, we'll overwrite it now terom@125: data_size += char_size; terom@125: terom@125: } else { terom@125: // continue counting the chars, but don't output anything anymore terom@125: data_size += str_quote_char(NULL, 0, *str++); terom@125: } terom@125: } terom@125: terom@125: // end quote terom@125: buf += str_advance(&data_size, &buf_size, str_append_char(buf, buf_size, '\'')); terom@125: } terom@125: terom@125: // overflow -> mark terom@125: if (buf_size < STR_QUOTE_RESERVED) terom@125: buf += str_advance(NULL, &buf_size, str_append(buf, buf_size, "...")); terom@125: terom@125: // the total outputted chars terom@125: return data_size; terom@125: } terom@125: terom@129: /** terom@129: * Output the data for a single parameter terom@129: */ terom@218: 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) terom@129: { terom@129: const char *value; terom@129: ssize_t value_len = -1; terom@129: char flag; terom@129: bool use_quote = false; terom@129: terom@129: // look it up terom@218: if (func(name, &value, &value_len, arg, err)) terom@218: return PUSH_ERROR(err, &str_errors, ERR_STR_FMT_VALUE); terom@129: terom@129: // not found? terom@129: if (!value) terom@218: return SET_ERROR(err, &str_errors, ERR_STR_FMT_NAME); terom@129: terom@129: // parse flags terom@129: while ((flag = *flags++)) { terom@129: switch (flag) { terom@129: case 'r': terom@129: // quote terom@129: use_quote = true; terom@129: terom@129: break; terom@129: terom@129: default: terom@129: // unknown flag terom@218: return SET_ERROR(err, &str_errors, ERR_STR_FMT_FLAG); terom@129: terom@129: } terom@129: } terom@129: terom@129: // output value terom@129: if (use_quote) terom@129: *buf += str_advance(NULL, buf_size, str_quote(*buf, *buf_size, value, value_len)); terom@129: terom@129: else terom@129: *buf += str_advance(NULL, buf_size, str_append_num(*buf, *buf_size, value, value_len)); terom@129: terom@129: // ok terom@129: return SUCCESS; terom@129: } terom@129: terom@216: err_t str_format (char *buf, size_t buf_size, const char *format, str_format_cb func, void *arg, error_t *err) terom@129: { terom@129: char name_buf[STR_FORMAT_PARAM_MAX + 1], *name_ptr; terom@129: size_t name_size; terom@129: char flags_buf[STR_FORMAT_FLAGS_MAX + 1], *flags_ptr; terom@129: size_t flags_size; terom@129: bool in_param = false, in_param_flags = false; terom@129: terom@129: // iterate over the format string terom@129: do { terom@129: // check buffer state terom@129: if (!buf_size) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_BUF_LEN); terom@129: terom@129: // inspect this char terom@129: switch (*format) { terom@129: case '{': terom@129: // syntax terom@129: if (in_param) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG); terom@129: terom@129: // init state terom@129: in_param = true; terom@129: name_ptr = name_buf; terom@129: name_size = sizeof(name_buf); terom@129: flags_ptr = flags_buf; terom@129: flags_size = sizeof(flags_buf); terom@129: terom@129: *name_ptr = *flags_ptr = '\0'; terom@129: terom@129: break; terom@129: terom@129: case '}': terom@129: // syntax terom@129: if (!in_param) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG); terom@129: terom@129: // reset state terom@129: in_param = false; terom@129: in_param_flags = false; terom@129: terom@129: // output token terom@216: if (str_format_param(&buf, &buf_size, name_buf, flags_buf, func, arg, err)) terom@216: return error_code(err); terom@129: terom@129: break; terom@129: terom@129: case ':': terom@129: if (in_param) { terom@129: // set state terom@129: in_param_flags = true; terom@129: terom@129: break; terom@129: } terom@129: terom@129: /* Fallthrough */ terom@129: terom@129: default: terom@129: if (in_param && in_param_flags ) { terom@129: // add to param flags terom@129: flags_ptr += str_advance(NULL, &flags_size, str_append_char(flags_ptr, flags_size, *format)); terom@129: terom@129: if (!flags_size) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_FLAGS_LEN); terom@129: terom@129: } else if (in_param) { terom@129: // add to param name terom@129: name_ptr += str_advance(NULL, &name_size, str_append_char(name_ptr, name_size, *format)); terom@129: terom@129: if (!name_size) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_NAME_LEN); terom@132: terom@129: } else { terom@129: // add to output terom@129: buf += str_advance(NULL, &buf_size, str_append_char(buf, buf_size, *format)); terom@129: terom@129: } terom@129: terom@129: break; terom@129: } terom@129: } while (*format++); terom@129: terom@129: // syntax terom@129: if (in_param) terom@216: return SET_ERROR(err, &str_errors, ERR_STR_FMT_TAG); terom@129: terom@129: // ok terom@129: return SUCCESS; terom@129: } terom@216: