diff --git a/src/string_renderer.c b/src/string_renderer.c index 2d7cd3b0..0bd72b49 100644 --- a/src/string_renderer.c +++ b/src/string_renderer.c @@ -65,11 +65,8 @@ igloo_RO_PUBLIC_TYPE(string_renderer_t, igloo_ro_full_t, igloo_RO_TYPEDECL_NEW(__string_renderer_new) ); -static igloo_error_t string_renderer_append_raw(string_renderer_t *self, const char *string, ssize_t len) +static inline igloo_error_t string_renderer_pre_allocate(string_renderer_t *self, size_t len) { - if (len < 0) - len = strlen(string); - if (self->len < (self->fill + len + 1)) { size_t new_len = self->len + len + 1 + 64; /* allocate more than we need to avoid re-allocating every time */ char *n = realloc(self->buffer, new_len); @@ -79,12 +76,36 @@ static igloo_error_t string_renderer_append_raw(string_renderer_t *self, const c self->len = new_len; } + return igloo_ERROR_NONE; +} + +static igloo_error_t string_renderer_append_raw(string_renderer_t *self, const char *string, ssize_t len) +{ + igloo_error_t err; + + if (len < 0) + len = strlen(string); + + err = string_renderer_pre_allocate(self, len); + if (err != igloo_ERROR_NONE) + return err; + memcpy(self->buffer + self->fill, string, len); self->fill += len; return igloo_ERROR_NONE; } +static inline igloo_error_t string_renderer_append_char(string_renderer_t *self, const char c) +{ + if (self->len < (self->fill + 1 + 1)) { + return string_renderer_append_raw(self, &c, 1); + } + + self->buffer[self->fill++] = c; + return igloo_ERROR_NONE; +} + igloo_error_t string_renderer_start_list(string_renderer_t *self, const char *record_separator, const char *kv_separator, bool allow_null_key, bool allow_null_value, string_renderer_encoding_t encoding) { if (!self) @@ -143,6 +164,52 @@ igloo_error_t string_renderer_add_string_with_options(string_renderer_t *s return err; } break; + case STRING_RENDERER_ENCODING_H: + case STRING_RENDERER_ENCODING_H_ALT: + case STRING_RENDERER_ENCODING_H_SPACE: + case STRING_RENDERER_ENCODING_H_ALT_SPACE: + if (!string) { + return string_renderer_append_char(self, '-'); + } else { + bool alt = encoding == STRING_RENDERER_ENCODING_H_ALT || encoding == STRING_RENDERER_ENCODING_H_ALT_SPACE; + bool space = encoding == STRING_RENDERER_ENCODING_H_SPACE || encoding == STRING_RENDERER_ENCODING_H_ALT_SPACE; + + /* ignore errors here as we just try to optimise access */ + string_renderer_pre_allocate(self, strlen(string)); + + if (alt) { + igloo_error_t err = string_renderer_append_char(self, '"'); + if (err != igloo_ERROR_NONE) + return err; + } + + for (const char *sp = string; *sp; sp++) { + const char c = *sp; + + /* copied from common/log/log.c __vsnprintf__is_print() */ + if ((c <= '"' || c == '`' || c == '\\') && !(space && c == ' ')) { + static const char hextable[] = "0123456789abcdef"; + char buf[4] = "\\xXX"; + buf[2] = hextable[(c >> 4) & 0x0F]; + buf[3] = hextable[(c >> 0) & 0x0F]; + igloo_error_t err = string_renderer_append_raw(self, buf, 4); + if (err != igloo_ERROR_NONE) + return err; + } else { + igloo_error_t err = string_renderer_append_char(self, c); + if (err != igloo_ERROR_NONE) + return err; + } + } + + if (alt) { + igloo_error_t err = string_renderer_append_char(self, '"'); + if (err != igloo_ERROR_NONE) + return err; + } + } + return igloo_ERROR_NONE; + break; default: return igloo_ERROR_INVAL; } @@ -171,6 +238,10 @@ igloo_error_t string_renderer_add_int_with_options(string_renderer_t *self switch (encoding) { case STRING_RENDERER_ENCODING_PLAIN: case STRING_RENDERER_ENCODING_URI: + case STRING_RENDERER_ENCODING_H: + case STRING_RENDERER_ENCODING_H_ALT: + case STRING_RENDERER_ENCODING_H_SPACE: + case STRING_RENDERER_ENCODING_H_ALT_SPACE: { char buffer[64]; int ret = snprintf(buffer, sizeof(buffer), "%lli", val); @@ -270,7 +341,7 @@ const char * string_renderer_to_string_zero_copy(string_renderer_t *self) return NULL; /* add a \0 to the end */ - if (string_renderer_append_raw(self, "\0", 1) != igloo_ERROR_NONE) + if (string_renderer_append_char(self, '\0') != igloo_ERROR_NONE) return NULL; /* but do not count it as fill */ diff --git a/src/string_renderer.h b/src/string_renderer.h index 85ca39e3..c79c2b2f 100644 --- a/src/string_renderer.h +++ b/src/string_renderer.h @@ -21,7 +21,11 @@ igloo_RO_FORWARD_TYPE(string_renderer_t); typedef enum { STRING_RENDERER_ENCODING_DEFAULT, STRING_RENDERER_ENCODING_PLAIN, - STRING_RENDERER_ENCODING_URI + STRING_RENDERER_ENCODING_URI, + STRING_RENDERER_ENCODING_H, /* same as "%H" */ + STRING_RENDERER_ENCODING_H_ALT, /* same as "%#H" */ + STRING_RENDERER_ENCODING_H_SPACE, /* same as "% H" */ + STRING_RENDERER_ENCODING_H_ALT_SPACE /* same as "%# H" */ } string_renderer_encoding_t; /* all functions are NOT thread safe */ diff --git a/src/tests/ctest_string_renderer.c b/src/tests/ctest_string_renderer.c new file mode 100644 index 00000000..53616ab9 --- /dev/null +++ b/src/tests/ctest_string_renderer.c @@ -0,0 +1,85 @@ +/* Icecast + * + * This program is distributed under the GNU General Public License, version 2. + * A copy of this license is included with this source. + * + * Copyright 2022, Philipp "ph3-der-loewe" Schafft , + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include /* for EXIT_FAILURE */ +#include /* for strcmp() */ + +#include "../icecasttypes.h" + +#include +#include +#include + +#include "../string_renderer.h" + +static igloo_ro_t g_instance; + +static void basic_test(void) +{ + string_renderer_t * renderer; + const char *res; + + igloo_tap_test_success("igloo_ro_new renderer", igloo_ro_new(&renderer, string_renderer_t, g_instance)); + + igloo_tap_test_success("string_renderer_add_string renderer \"te\"", string_renderer_add_string(renderer, "te")); + igloo_tap_test_success("string_renderer_add_string renderer \"st\"", string_renderer_add_string(renderer, "st")); + + res = string_renderer_to_string_zero_copy(renderer); + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns non-NULL", res != NULL); + if (res) { + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns \"test\"", strcmp(res, "test") == 0); + } + + igloo_tap_test_success("string_renderer_add_int renderer 133742", string_renderer_add_int(renderer, 133742)); + igloo_tap_test_success("string_renderer_start_list_formdata renderer", string_renderer_start_list_formdata(renderer)); + igloo_tap_test_success("string_renderer_add_kv renderer \"key\" \"val ue\"", string_renderer_add_kv(renderer, "key", "val ue")); + igloo_tap_test_success("string_renderer_add_ki renderer \"num\", -31415", string_renderer_add_ki(renderer, "num", -31415)); + igloo_tap_test_success("string_renderer_end_list renderer", string_renderer_end_list(renderer)); + + res = string_renderer_to_string_zero_copy(renderer); + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns non-NULL", res != NULL); + if (res) { + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns \"test133742key=val%20ue&num=-31415\"", strcmp(res, "test133742key=val%20ue&num=-31415") == 0); + } + + igloo_tap_test_success("string_renderer_add_string_with_options renderer \"te!s t\" false STRING_RENDERER_ENCODING_H", string_renderer_add_string_with_options(renderer, "te!s t", false, STRING_RENDERER_ENCODING_H)); + igloo_tap_test_success("string_renderer_add_string_with_options renderer \"te!s t\" false STRING_RENDERER_ENCODING_H_ALT", string_renderer_add_string_with_options(renderer, "te!s t", false, STRING_RENDERER_ENCODING_H_ALT)); + igloo_tap_test_success("string_renderer_add_string_with_options renderer \"te!s t\" false STRING_RENDERER_ENCODING_H_SPACE", string_renderer_add_string_with_options(renderer, "te!s t", false, STRING_RENDERER_ENCODING_H_SPACE)); + igloo_tap_test_success("string_renderer_add_string_with_options renderer \"te!s t\" false STRING_RENDERER_ENCODING_H_ALT_SPACE", string_renderer_add_string_with_options(renderer, "te!s t", false, STRING_RENDERER_ENCODING_H_ALT_SPACE)); + igloo_tap_test_success("string_renderer_add_string_with_options renderer NULL true STRING_RENDERER_ENCODING_H", string_renderer_add_string_with_options(renderer, NULL, true, STRING_RENDERER_ENCODING_H)); + + res = string_renderer_to_string_zero_copy(renderer); + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns non-NULL", res != NULL); + if (res) { + igloo_tap_test("string_renderer_to_string_zero_copy renderer returns \"test133742key=val%20ue&num=-31415te\\x21s\\x20t\"te\\x21s\\x20t\"te\\x21s t\"te\\x21s t\"-\"", strcmp(res, "test133742key=val%20ue&num=-31415te\\x21s\\x20t\"te\\x21s\\x20t\"te\\x21s t\"te\\x21s t\"-") == 0); + } + + igloo_tap_test_success("unref renderer", igloo_ro_unref(&renderer)); +} + +int main (void) +{ + igloo_tap_init(); + igloo_tap_exit_on(igloo_TAP_EXIT_ON_FIN, NULL); + igloo_tap_test_success("igloo_initialize", igloo_initialize(&g_instance)); + if (igloo_ro_is_null(g_instance)) { + igloo_tap_bail_out("Can not get an instance"); + return EXIT_FAILURE; // return failure as we should never reach this point! + } + + basic_test(); + + igloo_tap_test_success("unref instance", igloo_ro_unref(&g_instance)); + igloo_tap_fin(); + + return EXIT_FAILURE; // return failure as we should never reach this point! +}