1
0
mirror of https://gitlab.xiph.org/xiph/icecast-server.git synced 2024-06-23 06:25:24 +00:00
icecast-server/src/string_renderer.c
2022-09-17 17:17:34 +00:00

352 lines
10 KiB
C

/* 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 <lion@lion.leolix.org>,
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <stdlib.h>
#include "icecasttypes.h"
#include <igloo/ro.h>
#include <igloo/error.h>
#include "string_renderer.h"
#include "util.h"
#include "logging.h"
#define CATMODULE "string-renderer"
struct string_renderer_tag {
igloo_ro_full_t __parent;
/* config */
const char *record_separator;
const char *kv_separator;
bool allow_null_key;
bool allow_null_value;
string_renderer_encoding_t encoding;
/* state */
bool list_start;
char *buffer;
size_t fill;
size_t len;
};
static void __string_renderer_free(igloo_ro_t self)
{
string_renderer_t *renderer = igloo_ro_to_type(self, string_renderer_t);
free(renderer->buffer);
}
static igloo_error_t __string_renderer_new(igloo_ro_t self, const igloo_ro_type_t *type, va_list ap)
{
string_renderer_t *renderer = igloo_ro_to_type(self, string_renderer_t);
renderer->list_start = false;
renderer->record_separator = ", ";
renderer->kv_separator = "=";
renderer->allow_null_key = true;
renderer->allow_null_value = true;
renderer->encoding = STRING_RENDERER_ENCODING_PLAIN;
return igloo_ERROR_NONE;
}
igloo_RO_PUBLIC_TYPE(string_renderer_t, igloo_ro_full_t,
igloo_RO_TYPEDECL_FREE(__string_renderer_free),
igloo_RO_TYPEDECL_NEW(__string_renderer_new)
);
static inline igloo_error_t string_renderer_pre_allocate(string_renderer_t *self, size_t len)
{
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);
if (!n)
return igloo_ERROR_NOMEM;
self->buffer = n;
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)
return igloo_ERROR_FAULT;
self->list_start = true;
self->record_separator = record_separator;
self->kv_separator = kv_separator;
self->allow_null_key = allow_null_key;
self->allow_null_value = allow_null_value;
if (encoding != STRING_RENDERER_ENCODING_DEFAULT) {
self->encoding = encoding;
}
return igloo_ERROR_NONE;
}
igloo_error_t string_renderer_end_list(string_renderer_t *self)
{
/* no-op */
return igloo_ERROR_NONE;
}
igloo_error_t string_renderer_add_string_with_options(string_renderer_t *self, const char *string, bool allow_null, string_renderer_encoding_t encoding)
{
if (!self)
return igloo_ERROR_FAULT;
if (!string && !allow_null) {
return igloo_ERROR_INVAL;
}
if (encoding == STRING_RENDERER_ENCODING_DEFAULT) {
encoding = self->encoding;
}
switch (encoding) {
case STRING_RENDERER_ENCODING_PLAIN:
if (!string)
return igloo_ERROR_NONE;
return string_renderer_append_raw(self, string, -1);
break;
case STRING_RENDERER_ENCODING_URI:
if (!string) {
return igloo_ERROR_NONE;
} else {
char *x = util_url_escape(string);
igloo_error_t err;
if (!x)
return igloo_ERROR_NOMEM;
err = string_renderer_append_raw(self, x, -1);
free(x);
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;
}
/* this should never be reached */
return igloo_ERROR_GENERIC;
}
igloo_error_t string_renderer_add_int_with_options(string_renderer_t *self, long long int val, bool allow_zero, bool allow_negative, string_renderer_encoding_t encoding)
{
if (!self)
return igloo_ERROR_FAULT;
if (val == 0 && !allow_zero) {
return igloo_ERROR_INVAL;
}
if (val < 0 && !allow_negative) {
return igloo_ERROR_INVAL;
}
if (encoding == STRING_RENDERER_ENCODING_DEFAULT) {
encoding = self->encoding;
}
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);
if (ret < 1 || ret > (int)sizeof(buffer))
return igloo_ERROR_GENERIC;
return string_renderer_append_raw(self, buffer, ret);
}
break;
default:
return igloo_ERROR_INVAL;
}
/* this should never be reached */
return igloo_ERROR_GENERIC;
}
static igloo_error_t string_renderer_add_kv_key_only(string_renderer_t *self, const char *key, string_renderer_encoding_t encoding)
{
if (!self)
return igloo_ERROR_FAULT;
if (!key && !self->allow_null_key) {
return igloo_ERROR_INVAL;
}
if (encoding == STRING_RENDERER_ENCODING_DEFAULT) {
encoding = self->encoding;
}
if (self->list_start) {
self->list_start = false;
} else {
igloo_error_t err = string_renderer_append_raw(self, self->record_separator, -1);
if (err != igloo_ERROR_NONE)
return err;
}
return string_renderer_add_string_with_options(self, key, true, encoding);
}
igloo_error_t string_renderer_add_kv_with_options(string_renderer_t *self, const char *key, const char *value, string_renderer_encoding_t key_encoding, bool allow_null_value, bool allow_empty_value)
{
igloo_error_t err;
if (!self)
return igloo_ERROR_FAULT;
if (!value && !(allow_null_value && self->allow_null_value)) {
return igloo_ERROR_INVAL;
}
if (value && !*value && !allow_empty_value) {
return igloo_ERROR_INVAL;
}
err = string_renderer_add_kv_key_only(self, key, key_encoding);
if (err != igloo_ERROR_NONE)
return err;
err = string_renderer_append_raw(self, self->kv_separator, -1);
if (err != igloo_ERROR_NONE)
return err;
return string_renderer_add_string_with_options(self, value, allow_null_value, STRING_RENDERER_ENCODING_DEFAULT);
}
igloo_error_t string_renderer_add_ki_with_options(string_renderer_t *self, const char *key, long long int value, string_renderer_encoding_t key_encoding, bool allow_zero, bool allow_negative)
{
igloo_error_t err;
if (!self)
return igloo_ERROR_FAULT;
if (value == 0 && !allow_zero) {
return igloo_ERROR_INVAL;
}
if (value < 0 && !allow_negative) {
return igloo_ERROR_INVAL;
}
err = string_renderer_add_kv_key_only(self, key, key_encoding);
if (err != igloo_ERROR_NONE)
return err;
err = string_renderer_append_raw(self, self->kv_separator, -1);
if (err != igloo_ERROR_NONE)
return err;
return string_renderer_add_int_with_options(self, value, allow_zero, allow_negative, STRING_RENDERER_ENCODING_DEFAULT);
}
const char * string_renderer_to_string_zero_copy(string_renderer_t *self)
{
if (!self)
return NULL;
/* add a \0 to the end */
if (string_renderer_append_char(self, '\0') != igloo_ERROR_NONE)
return NULL;
/* but do not count it as fill */
self->fill--;
return self->buffer;
}