Fri, 20 Dec 2024 15:00:05 +0100
add cxBufferAppend() - fixes #534
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2024 Mike Becker, Olaf Wintermann All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "cx/properties.h" #include <assert.h> static const int CX_PROPERTIES_FLAG_USE_STACK = 0x01; const CxPropertiesConfig cx_properties_config_default = { '=', //'\\', '#', '\0', '\0' }; void cxPropertiesInit( CxProperties *prop, CxPropertiesConfig config ) { memset(prop, 0, sizeof(CxProperties)); prop->config = config; } void cxPropertiesDestroy(CxProperties *prop) { if (0 == (prop->flags & CX_PROPERTIES_FLAG_USE_STACK)) { free(prop->buf); } prop->buf = NULL; prop->buf_capacity = prop->buf_size = 0; } static int cx_properties_ensure_buf_capacity(CxProperties *prop, size_t cap) { if (prop->buf_capacity >= cap) { return 0; } // not enough capacity - are we on the stack right now? if ((prop->flags & CX_PROPERTIES_FLAG_USE_STACK) != 0) { // move to the heap char *newbuf = malloc(cap); if (newbuf == NULL) return 1; memcpy(newbuf, prop->buf, prop->buf_size); prop->buf = newbuf; prop->flags &= CX_PROPERTIES_FLAG_USE_STACK; } else { // we are on the heap already, reallocate // this is legit, because realloc() behaves like malloc() when the // current pointer is NULL char *newbuf = realloc(prop->buf, cap); if (newbuf == NULL) return 1; prop->buf = newbuf; } // store new capacity and return prop->buf_capacity = cap; return 0; } static int cx_properties_rescuen_input(CxProperties *prop, size_t len) { if (cx_properties_ensure_buf_capacity(prop, prop->buf_size + len)) { return 1; } const char *src = prop->text + prop->text_pos; char *dest = prop->buf + prop->buf_size; memcpy(dest, src, len); prop->buf_size += len; prop->text_pos += len; return 0; } static int cx_properties_rescue_input(CxProperties *prop) { // someone fucked around with our integers, exit immediately if (prop->text_pos > prop->text_size) return 0; // determine the bytes needed size_t len = prop->text_size - prop->text_pos; return cx_properties_rescuen_input(prop, len); } int cxPropertiesFilln( CxProperties *prop, const char *buf, size_t len ) { if (cx_properties_rescue_input(prop)) return 1; prop->text = buf; prop->text_size = len; prop->text_pos = 0; return 0; } void cxPropertiesUseStack( CxProperties *prop, char *buf, size_t capacity ) { assert(prop->buf == NULL); prop->buf = buf; prop->buf_capacity = capacity; prop->buf_size = 0; prop->flags |= CX_PROPERTIES_FLAG_USE_STACK; } CxPropertiesStatus cxPropertiesNext( CxProperties *prop, cxstring *key, cxstring *value ) { // check if we have a text buffer if (prop->text == NULL) { return CX_PROPERTIES_NULL_INPUT; } // check if we have rescued data if (prop->buf_size > 0) { // check if we can now get a complete line const char *buf = prop->text + prop->text_pos; size_t len = prop->text_size - prop->text_pos; cxstring str = cx_strn(buf, len); cxstring nl = cx_strchr(str, '\n'); if(nl.length > 0) { // we add as much data to the rescue buffer as we need // to complete the line size_t len_until_nl = (size_t)(nl.ptr - buf) + 1; if (cx_properties_rescuen_input(prop, len_until_nl)) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } // the tmp buffer contains exactly one line now // we use a trick here: we swap the buffers and recurse const char *orig_text = prop->text; size_t orig_size = prop->text_size; prop->text = prop->buf; prop->text_size = prop->buf_size; prop->text_pos = 0; prop->buf_size = 0; CxPropertiesStatus result; result = cxPropertiesNext(prop, key, value); // restore original buffer prop->text = orig_text; prop->text_size = orig_size; // set the position to after the newline prop->text_pos = len_until_nl; // check the result if (result == CX_PROPERTIES_NO_ERROR) { // reset the rescue buffer and return with the result prop->buf_size = 0; return result; } else if (result == CX_PROPERTIES_NO_DATA) { // rescue buffer contained only blanks or comments // reset the rescue buffer and retry with text buffer prop->buf_size = 0; return cxPropertiesNext(prop, key, value); } else { // CX_PROPERTIES_INCOMPLETE_DATA is not possible // so it must have been another error // do not reset the rescue buffer and return the error return result; } } else { // still not enough data if (cx_properties_rescue_input(prop)) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } return CX_PROPERTIES_INCOMPLETE_DATA; } } char comment1 = prop->config.comment1; char comment2 = prop->config.comment2; char comment3 = prop->config.comment3; char delimiter = prop->config.delimiter; // get one line and parse it while (prop->text_pos < prop->text_size) { const char *buf = prop->text + prop->text_pos; size_t len = prop->text_size - prop->text_pos; /* * First we check if we have at least one line. We also get indices of * delimiter and comment chars */ size_t delimiter_index = 0; size_t comment_index = 0; bool has_comment = false; size_t i = 0; char c = 0; for (; i < len; i++) { c = buf[i]; if (c == comment1 || c == comment2 || c == comment3) { if (comment_index == 0) { comment_index = i; has_comment = true; } } else if (c == delimiter) { if (delimiter_index == 0 && !has_comment) { delimiter_index = i; } } else if (c == '\n') { break; } } if (c != '\n') { // we don't have enough data for a line if (cx_properties_rescue_input(prop)) { return CX_PROPERTIES_BUFFER_ALLOC_FAILED; } return CX_PROPERTIES_INCOMPLETE_DATA; } cxstring line = has_comment ? cx_strn(buf, comment_index) : cx_strn(buf, i); // check line if (delimiter_index == 0) { // if line is not blank ... line = cx_strtrim(line); // ... either no delimiter found, or key is empty if (line.length > 0) { if (line.ptr[0] == delimiter) { return CX_PROPERTIES_INVALID_EMPTY_KEY; } else { return CX_PROPERTIES_INVALID_MISSING_DELIMITER; } } } else { cxstring k = cx_strn(buf, delimiter_index); cxstring val = cx_strn( buf + delimiter_index + 1, line.length - delimiter_index - 1); k = cx_strtrim(k); val = cx_strtrim(val); if (k.length > 0) { *key = k; *value = val; prop->text_pos += i + 1; assert(prop->text_pos <= prop->text_size); return CX_PROPERTIES_NO_ERROR; } else { return CX_PROPERTIES_INVALID_EMPTY_KEY; } } prop->text_pos += i + 1; } // when we come to this point, all data must have been read assert(prop->text_pos == prop->text_size); return CX_PROPERTIES_NO_DATA; } static int cx_properties_sink_map( cx_attr_unused CxProperties *prop, CxPropertiesSink *sink, cxstring key, cxstring value ) { CxMap *map = sink->sink; CxAllocator *alloc = sink->data; cxmutstr v = cx_strdup_a(alloc, value); int r = cx_map_put_cxstr(map, key, v.ptr); if (r != 0) cx_strfree_a(alloc, &v); return r; } CxPropertiesSink cxPropertiesMapSink(CxMap *map) { CxPropertiesSink sink; sink.sink = map; sink.data = cxDefaultAllocator; sink.sink_func = cx_properties_sink_map; return sink; } static int cx_properties_read_string( CxProperties *prop, CxPropertiesSource *src, cxstring *target ) { if (prop->text == src->src) { // when the input buffer already contains the string // we have nothing more to provide target->length = 0; } else { target->ptr = src->src; target->length = src->data_size; } return 0; } static int cx_properties_read_file( cx_attr_unused CxProperties *prop, CxPropertiesSource *src, cxstring *target ) { target->ptr = src->data_ptr; target->length = fread(src->data_ptr, 1, src->data_size, src->src); return ferror(src->src); } static int cx_properties_read_init_file( cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { src->data_ptr = malloc(src->data_size); if (src->data_ptr == NULL) return 1; return 0; } static void cx_properties_read_clean_file( cx_attr_unused CxProperties *prop, CxPropertiesSource *src ) { free(src->data_ptr); } CxPropertiesSource cxPropertiesStringSource(cxstring str) { CxPropertiesSource src; src.src = (void*) str.ptr; src.data_size = str.length; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; return src; } CxPropertiesSource cxPropertiesCstrnSource(const char *str, size_t len) { CxPropertiesSource src; src.src = (void*) str; src.data_size = len; src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; return src; } CxPropertiesSource cxPropertiesCstrSource(const char *str) { CxPropertiesSource src; src.src = (void*) str; src.data_size = strlen(str); src.read_func = cx_properties_read_string; src.read_init_func = NULL; src.read_clean_func = NULL; return src; } CxPropertiesSource cxPropertiesFileSource(FILE *file, size_t chunk_size) { CxPropertiesSource src; src.src = file; src.data_size = chunk_size; src.read_func = cx_properties_read_file; src.read_init_func = cx_properties_read_init_file; src.read_clean_func = cx_properties_read_clean_file; return src; } CxPropertiesStatus cxPropertiesLoad( CxProperties *prop, CxPropertiesSink sink, CxPropertiesSource source ) { assert(source.read_func != NULL); assert(sink.sink_func != NULL); // initialize reader if (source.read_init_func != NULL) { if (source.read_init_func(prop, &source)) { return CX_PROPERTIES_READ_INIT_FAILED; } } // transfer the data from the source to the sink CxPropertiesStatus status; bool found = false; while (true) { // read input cxstring input; if (source.read_func(prop, &source, &input)) { status = CX_PROPERTIES_READ_FAILED; break; } // no more data - break if (input.length == 0) { status = found ? CX_PROPERTIES_NO_ERROR : CX_PROPERTIES_NO_DATA; break; } // set the input buffer and read the k/v-pairs prop->text = input.ptr; prop->text_size = input.length; prop->text_pos = 0; CxPropertiesStatus kv_status; do { cxstring key, value; kv_status = cxPropertiesNext(prop, &key, &value); if (kv_status == CX_PROPERTIES_NO_ERROR) { found = true; if (sink.sink_func(prop, &sink, key, value)) { kv_status = CX_PROPERTIES_SINK_FAILED; } } } while (kv_status == CX_PROPERTIES_NO_ERROR); if (kv_status > CX_PROPERTIES_OK) { status = kv_status; break; } } if (source.read_clean_func != NULL) { source.read_clean_func(prop, &source); } return status; }