/* -*- Mode: c; c-basic-offset: 2 -*- * * raptor_stringbuffer.c - Stringbuffer class for growing strings * * Copyright (C) 2003-2008, David Beckett http://www.dajobe.org/ * Copyright (C) 2003-2004, University of Bristol, UK http://www.bristol.ac.uk/ * * This package is Free Software and part of Redland http://librdf.org/ * * It is licensed under the following three licenses as alternatives: * 1. GNU Lesser General Public License (LGPL) V2.1 or any newer version * 2. GNU General Public License (GPL) V2 or any newer version * 3. Apache License, V2.0 or any newer version * * You may not use this file except in compliance with at least one of * the above three licenses. * * See LICENSE.html or LICENSE.txt at the top of this package for the * complete terms and further detail along with the license texts for * the licenses in COPYING.LIB, COPYING and LICENSE-2.0.txt respectively. * * * */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #ifdef HAVE_STDLIB_H #include /* for abort() as used in errors */ #endif /* Raptor includes */ #include "raptor2.h" #include "raptor_internal.h" #ifndef STANDALONE struct raptor_stringbuffer_node_s { struct raptor_stringbuffer_node_s* next; unsigned char *string; size_t length; }; typedef struct raptor_stringbuffer_node_s raptor_stringbuffer_node; struct raptor_stringbuffer_s { /* Pointing to the first item in the list of nodes */ raptor_stringbuffer_node* head; /* and the last */ raptor_stringbuffer_node* tail; /* total length of the string */ size_t length; /* frozen string if already calculated, or NULL if not present */ unsigned char *string; }; /* prototypes for local functions */ static int raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy); /* functions implementing the stringbuffer api */ /** * raptor_new_stringbuffer: * * Create a new stringbuffer. * * Return value: pointer to a raptor_stringbuffer object or NULL on failure **/ raptor_stringbuffer* raptor_new_stringbuffer(void) { raptor_stringbuffer* sb; sb = RAPTOR_CALLOC(raptor_stringbuffer*, 1, sizeof(*sb)); return sb; } /** * raptor_free_stringbuffer: * @stringbuffer: stringbuffer object to destroy. * * Destroy a stringbuffer. * **/ void raptor_free_stringbuffer(raptor_stringbuffer *stringbuffer) { if(!stringbuffer) return; if(stringbuffer->head) { raptor_stringbuffer_node *node = stringbuffer->head; while(node) { raptor_stringbuffer_node *next = node->next; if(node->string) RAPTOR_FREE(char*, node->string); RAPTOR_FREE(raptor_stringbuffer_node, node); node = next; } } if(stringbuffer->string) RAPTOR_FREE(char*, stringbuffer->string); RAPTOR_FREE(raptor_stringbuffer, stringbuffer); } /** * raptor_stringbuffer_append_string_common: * @stringbuffer: raptor stringbuffer * @string: string * @length: length of string * @do_copy: non-0 to copy the string * * Add a string to the stringbuffer. * * INTERNAL * * If @string is NULL or @length is 0, no work is performed. * * If @do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Return value: non-0 on failure **/ static int raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy) { raptor_stringbuffer_node *node; if(!string || !length) return 0; node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node)); if(!node) { if(!do_copy) RAPTOR_FREE(char*, string); return 1; } if(do_copy) { /* Note this copy does not include the \0 character - not needed */ node->string = RAPTOR_MALLOC(unsigned char*, length); if(!node->string) { RAPTOR_FREE(raptor_stringbuffer_node, node); return 1; } memcpy(node->string, string, length); } else node->string = (unsigned char*)string; node->length = length; if(stringbuffer->tail) { stringbuffer->tail->next = node; stringbuffer->tail = node; } else stringbuffer->head = stringbuffer->tail = node; node->next = NULL; if(stringbuffer->string) { RAPTOR_FREE(char*, stringbuffer->string); stringbuffer->string = NULL; } stringbuffer->length += length; return 0; } /** * raptor_stringbuffer_append_counted_string: * @stringbuffer: raptor stringbuffer * @string: string * @length: length of string * @do_copy: non-0 to copy the string * * Add a counted string to the stringbuffer. * * If @string is NULL or @length is 0, no work is performed. * * If @do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_counted_string(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy) { if(!string || !length) return 0; return raptor_stringbuffer_append_string_common(stringbuffer, string, length, do_copy); } /** * raptor_stringbuffer_append_string: * @stringbuffer: raptor stringbuffer * @string: string * @do_copy: non-0 to copy the string * * Add a string to the stringbuffer. * * If @string is NULL, no work is performed. * * If @do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_string(raptor_stringbuffer* stringbuffer, const unsigned char *string, int do_copy) { if(!string) return 0; return raptor_stringbuffer_append_string_common(stringbuffer, string, strlen((const char*)string), do_copy); } /** * raptor_stringbuffer_append_decimal: * @stringbuffer: raptor stringbuffer * @integer: integer to format as decimal and add * * Add an integer in decimal to the stringbuffer. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_decimal(raptor_stringbuffer* stringbuffer, int integer) { /* enough for 64 bit signed integer * INT64_MAX is 9223372036854775807 (19 digits) + 1 for sign */ unsigned char buf[20]; unsigned char *p; int i = integer; size_t length = 1; if(integer < 0) { length++; i= -integer; } while(i /= 10) length++; p = buf+length-1; i = integer; if(i < 0) i= -i; do { *p-- = RAPTOR_GOOD_CAST(unsigned char, '0'+(i %10)); i /= 10; } while(i); if(integer < 0) *p= '-'; return raptor_stringbuffer_append_counted_string(stringbuffer, buf, length, 1); } /** * raptor_stringbuffer_append_stringbuffer: * @stringbuffer: #raptor_stringbuffer * @append: #raptor_stringbuffer to append * * Add a stringbuffer to the stringbuffer. * * This function removes the content from the appending stringbuffer, * making it empty and appends it to the supplied stringbuffer. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_stringbuffer(raptor_stringbuffer* stringbuffer, raptor_stringbuffer* append) { raptor_stringbuffer_node *node = append->head; if(!node) return 0; /* move all append nodes to stringbuffer */ if(stringbuffer->tail) { stringbuffer->tail->next = node; } else stringbuffer->head = node; stringbuffer->tail = append->tail; /* adjust our length */ stringbuffer->length += append->length; if(stringbuffer->string) { RAPTOR_FREE(char*, stringbuffer->string); stringbuffer->string = NULL; } /* zap append content */ append->head = append->tail = NULL; append->length = 0; if(append->string) { RAPTOR_FREE(char*, append->string); append->string = NULL; } return 0; } /** * raptor_stringbuffer_prepend_string_common: * @stringbuffer: raptor stringbuffer * @string: string * @length: length of string * @do_copy: non-0 to copy the string * * Add a string to the start of a stringbuffer. * * INTERNAL * * If do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Return value: non-0 on failure **/ static int raptor_stringbuffer_prepend_string_common(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy) { raptor_stringbuffer_node *node; node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node)); if(!node) return 1; if(do_copy) { /* Note this copy does not include the \0 character - not needed */ node->string = RAPTOR_MALLOC(unsigned char*, length); if(!node->string) { RAPTOR_FREE(raptor_stringbuffer_node, node); return 1; } memcpy(node->string, string, length); } else node->string = (unsigned char*)string; node->length = length; node->next = stringbuffer->head; if(stringbuffer->head) stringbuffer->head = node; else stringbuffer->head = stringbuffer->tail = node; if(stringbuffer->string) { RAPTOR_FREE(char*, stringbuffer->string); stringbuffer->string = NULL; } stringbuffer->length += length; return 0; } /** * raptor_stringbuffer_prepend_counted_string: * @stringbuffer: raptor stringbuffer * @string: string * @length: length of string * @do_copy: non-0 to copy the string * If do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Add a string to the start of the stringbuffer. * * Return value: non-0 on failure **/ int raptor_stringbuffer_prepend_counted_string(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy) { return raptor_stringbuffer_prepend_string_common(stringbuffer, string, length, do_copy); } /** * raptor_stringbuffer_prepend_string: * @stringbuffer: raptor stringbuffer * @string: string * @do_copy: non-0 to copy the string * * Add a string to the start of the stringbuffer. * * If do_copy is non-0, the passed-in string is copied into new memory * otherwise the stringbuffer becomes the owner of the string pointer * and will free it when the stringbuffer is destroyed. * * Return value: non-0 on failure **/ int raptor_stringbuffer_prepend_string(raptor_stringbuffer* stringbuffer, const unsigned char *string, int do_copy) { return raptor_stringbuffer_prepend_string_common(stringbuffer, string, strlen((const char*)string), do_copy); } /** * raptor_stringbuffer_length: * @stringbuffer: raptor stringbuffer * * Return the stringbuffer length. * * Return value: size of stringbuffer **/ size_t raptor_stringbuffer_length(raptor_stringbuffer* stringbuffer) { return stringbuffer->length; } /** * raptor_stringbuffer_as_string: * @stringbuffer: raptor stringbuffer * * Return the stringbuffer as a C string. * * Note: the return value is a to a shared string that the stringbuffer * allocates and manages. * * Return value: NULL on failure or stringbuffer is empty, otherwise * a pointer to a shared copy of the string. **/ unsigned char * raptor_stringbuffer_as_string(raptor_stringbuffer* stringbuffer) { raptor_stringbuffer_node *node; unsigned char *p; if(!stringbuffer->length) return NULL; if(stringbuffer->string) return stringbuffer->string; stringbuffer->string = RAPTOR_MALLOC(unsigned char*, stringbuffer->length + 1); if(!stringbuffer->string) return NULL; node = stringbuffer->head; p = stringbuffer->string; while(node) { memcpy(p, node->string, node->length); p+= node->length; node = node->next; } *p='\0'; return stringbuffer->string; } /** * raptor_stringbuffer_copy_to_string: * @stringbuffer: raptor stringbuffer * @string: output string * @length: size of output string * * Copy the stringbuffer into a string. * * Copies the underlying string to a pre-allocated buffer. The * output string is always '\0' terminated. * * Return value: non-0 on failure such as stringbuffer is empty, buffer is too small **/ int raptor_stringbuffer_copy_to_string(raptor_stringbuffer* stringbuffer, unsigned char *string, size_t length) { raptor_stringbuffer_node *node; unsigned char *p; if(!string || length < 1) return 1; if(!stringbuffer->length) return 0; p = string; for(node = stringbuffer->head; node; node = node->next) { if(node->length > length) { p[-1]='\0'; return 1; } memcpy(p, node->string, node->length); p+= node->length; length-= node->length; } *p='\0'; return 0; } /** * raptor_stringbuffer_append_hexadecimal: * @stringbuffer: raptor stringbuffer * @hex: integer to format * * Add an integer formatted in hexdecimal (base 16) to the stringbuffer. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_hexadecimal(raptor_stringbuffer* stringbuffer, int hex) { unsigned char buf[2]; if(hex < 0 || hex > 0xF) return 1; *buf = RAPTOR_GOOD_CAST(unsigned char, (hex < 10) ? ('0' + hex) : ('A' + hex - 10)); buf[1] = '\0'; return raptor_stringbuffer_append_counted_string(stringbuffer, buf, 1, 1); } /* RFC3986 Unreserved */ #define IS_URI_UNRESERVED(c) ( (c >= 'A' && c <= 'F') || \ (c >= 'a' && c <= 'f') || \ (c >= '0' && c <= '9') || \ (c == '-' || c == '.' || c == '_' || c == '~') ) #define IS_URI_SAFE(c) (IS_URI_UNRESERVED(c)) /** * raptor_stringbuffer_append_uri_escaped_counted_string: * @sb: raptor stringbuffer * @string: string * @length: length of string * @space_is_plus: if non-0, escape spaces as '+' otherwise percent-encode them * * Add a URI-escaped version of @string to the stringbuffer. * * If @string is NULL or @length is 0, no work is performed. * * Return value: non-0 on failure **/ int raptor_stringbuffer_append_uri_escaped_counted_string(raptor_stringbuffer* sb, const char* string, size_t length, int space_is_plus) { unsigned int i; unsigned char buf[2]; buf[1] = '\0'; if(!string || !length) return 0; for(i = 0; i < length; i++) { char c = string[i]; if(!c) break; if(IS_URI_SAFE(c)) { *buf = RAPTOR_GOOD_CAST(unsigned char, c); if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1)) return 1; } else if (c == ' ' && space_is_plus) { *buf = '+'; if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1)) return 1; } else { *buf = '%'; if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1)) return 1; if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0xf0) >> 4)) return 1; if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0x0f))) return 1; } } return 0; } #endif #ifdef STANDALONE /* one more prototype */ int main(int argc, char *argv[]); int main(int argc, char *argv[]) { const char *program = raptor_basename(argv[0]); #define TEST_ITEMS_COUNT 9 const char *items[TEST_ITEMS_COUNT] = { "the", "quick" ,"brown", "fox", "jumps", "over", "the", "lazy", "dog" }; const char *items_string = "thequickbrownfoxjumpsoverthelazydog"; const size_t items_len = 35; const char *test_integer_string = "abcd"; #define TEST_INTEGERS_COUNT 7 const int test_integers[TEST_INTEGERS_COUNT]={ 0, 1, -1, 11, 1234, 12345, -12345 }; const char *test_integer_results[TEST_INTEGERS_COUNT]={ "abcd0", "abcd1", "abcd-1", "abcd11", "abcd1234", "abcd12345", "abcd-12345" }; raptor_stringbuffer *sb; unsigned char *str; size_t len; int i = 0; raptor_stringbuffer *sb1, *sb2; #define TEST_APPEND_COUNT 2 const char *test_append_results[TEST_APPEND_COUNT]={ "thebrownjumpsthedog", "quickfoxoverlazy" }; const char *test_append_results_total="thebrownjumpsthedogquickfoxoverlazy"; #define COPY_STRING_BUFFER_SIZE 100 unsigned char *copy_string; #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Creating string buffer\n", program); #endif /* test appending */ sb = raptor_new_stringbuffer(); if(!sb) { fprintf(stderr, "%s: Failed to create string buffer\n", program); exit(1); } for(i = 0; i < TEST_ITEMS_COUNT; i++) { int rc; len = strlen(items[i]); #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Adding string buffer item '%s'\n", program, items[i]); #endif rc = raptor_stringbuffer_append_counted_string(sb, (unsigned char*)items[i], len, 1); if(rc) { fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n", program, i, items[i], rc); exit(1); } } len = raptor_stringbuffer_length(sb); if(len != items_len) { fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program, (int)len, (int)items_len); exit(1); } str = raptor_stringbuffer_as_string(sb); if(strcmp((const char*)str, items_string)) { fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n", program, str, items_string); exit(1); } raptor_free_stringbuffer(sb); /* test prepending */ #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Creating string buffer\n", program); #endif sb = raptor_new_stringbuffer(); if(!sb) { fprintf(stderr, "%s: Failed to create string buffer\n", program); exit(1); } for(i = TEST_ITEMS_COUNT-1; i>=0 ; i--) { int rc; len = strlen(items[i]); #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Prepending string buffer item '%s'\n", program, items[i]); #endif rc = raptor_stringbuffer_prepend_counted_string(sb, (unsigned char*)items[i], len, 1); if(rc) { fprintf(stderr, "%s: Prepending string buffer item %d '%s' failed, returning error %d\n", program, i, items[i], rc); exit(1); } } len = raptor_stringbuffer_length(sb); if(len != items_len) { fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program, (int)len, (int)items_len); exit(1); } str = raptor_stringbuffer_as_string(sb); if(strcmp((const char*)str, items_string)) { fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n", program, str, items_string); exit(1); } /* test adding integers */ for(i = 0; i < TEST_INTEGERS_COUNT; i++) { raptor_stringbuffer *isb = raptor_new_stringbuffer(); if(!isb) { fprintf(stderr, "%s: Failed to create string buffer\n", program); exit(1); } raptor_stringbuffer_append_string(isb, (const unsigned char*)test_integer_string, 1); #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Adding decimal integer %d to buffer\n", program, test_integers[i]); #endif raptor_stringbuffer_append_decimal(isb, test_integers[i]); str = raptor_stringbuffer_as_string(isb); if(strcmp((const char*)str, test_integer_results[i])) { fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n", program, str, test_integer_results[i]); exit(1); } #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Freeing string buffer\n", program); #endif raptor_free_stringbuffer(isb); } #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Creating two stringbuffers to join\n", program); #endif sb1 = raptor_new_stringbuffer(); if(!sb1) { fprintf(stderr, "%s: Failed to create string buffer\n", program); exit(1); } sb2 = raptor_new_stringbuffer(); if(!sb2) { fprintf(stderr, "%s: Failed to create string buffer\n", program); exit(1); } for(i = 0; i < TEST_ITEMS_COUNT; i++) { raptor_stringbuffer *sbx; int rc; len = strlen(items[i]); sbx = (i % 2) ? sb2 : sb1; rc = raptor_stringbuffer_append_counted_string(sbx, (unsigned char*)items[i], len, 1); if(rc) { fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n", program, i, items[i], rc); exit(1); } } if(1) { int rc; rc = raptor_stringbuffer_append_counted_string(sb1, (unsigned char*)"X", 0, 1); if(rc) { fprintf(stderr, "%s: Adding 0-length counted string failed, returning error %d\n", program, rc); exit(1); } rc = raptor_stringbuffer_append_string(sb1, NULL, 1); if(rc) { fprintf(stderr, "%s: Adding NULL string failed, returning error %d\n", program, rc); exit(1); } } str = raptor_stringbuffer_as_string(sb1); if(strcmp((const char*)str, test_append_results[0])) { fprintf(stderr, "%s: string buffer sb1 contains '%s', expected '%s'\n", program, str, test_append_results[0]); exit(1); } str = raptor_stringbuffer_as_string(sb2); if(strcmp((const char*)str, test_append_results[1])) { fprintf(stderr, "%s: string buffer sb2 contains '%s', expected '%s'\n", program, str, test_append_results[1]); exit(1); } #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Appended two stringbuffers\n", program); #endif if(raptor_stringbuffer_append_stringbuffer(sb1, sb2)) { fprintf(stderr, "%s: Failed to append string buffer\n", program); exit(1); } str = raptor_stringbuffer_as_string(sb1); if(strcmp((const char*)str, test_append_results_total)) { fprintf(stderr, "%s: appended string buffer contains '%s', expected '%s'\n", program, str, test_append_results_total); exit(1); } len = raptor_stringbuffer_length(sb2); if(len) { fprintf(stderr, "%s: appended string buffer is length %d, not empty'\n", program, (int)len); exit(1); } #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Copying string buffer to string\n", program); #endif copy_string = (unsigned char*)malloc(COPY_STRING_BUFFER_SIZE); if(raptor_stringbuffer_copy_to_string(sb1, copy_string, COPY_STRING_BUFFER_SIZE)) { fprintf(stderr, "%s: copying string buffer to string failed\n", program); exit(1); } if(strcmp((const char*)copy_string, test_append_results_total)) { fprintf(stderr, "%s: copied string buffer contains '%s', expected '%s'\n", program, copy_string, test_append_results_total); exit(1); } free(copy_string); #if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1 fprintf(stderr, "%s: Freeing string buffers\n", program); #endif raptor_free_stringbuffer(sb1); raptor_free_stringbuffer(sb2); raptor_free_stringbuffer(sb); /* keep gcc -Wall happy */ return(0); } #endif