/* * Copyright (c) 2008 - 2010 * Jonathan Schleifer <js@webkeks.org> * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE included in * the packaging of this file. */ #include "config.h" #define _GNU_SOURCE #include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #ifdef HAVE_MADVISE # include <sys/mman.h> #else # define madvise(addr, len, advise) #endif #import "OFString.h" #import "OFExceptions.h" #import "macros.h" #import "asprintf.h" #import "unicode.h" static void apply_table(id self, Class isa, char **string, unsigned int *length, BOOL is_utf8, const of_unichar_t* const table[], const size_t table_size) { of_unichar_t c; of_unichar_t *ustr; size_t ulen, nlen, clen; size_t i, j, d; char *nstr; if (!is_utf8) { assert(table_size >= 1); uint8_t *p = (uint8_t*)*string + *length; uint8_t t; while (--p >= (uint8_t*)*string) if ((t = table[0][*p]) != 0) *p = t; return; } ulen = [self length]; ustr = [self allocMemoryForNItems: [self length] withSize: ulen]; i = 0; j = 0; nlen = 0; while (i < *length) { clen = of_string_utf8_to_unicode(*string + i, *length - i, &c); if (clen == 0 || c > 0x10FFFF) { [self freeMemory: ustr]; @throw [OFInvalidEncodingException newWithClass: isa]; } if (c >> 8 < table_size) { of_unichar_t tc = table[c >> 8][c & 0xFF]; if (tc) c = tc; } ustr[j++] = c; if (c < 0x80) nlen++; else if (c < 0x800) nlen += 2; else if (c < 0x10000) nlen += 3; else if (c < 0x110000) nlen += 4; else { [self freeMemory: ustr]; @throw [OFInvalidEncodingException newWithClass: isa]; } i += clen; } @try { nstr = [self allocMemoryWithSize: nlen + 1]; } @catch (OFException *e) { [self freeMemory: ustr]; @throw e; } j = 0; for (i = 0; i < ulen; i++) { if ((d = of_string_unicode_to_utf8(ustr[i], nstr + j)) == 0) { [self freeMemory: ustr]; [self freeMemory: nstr]; @throw [OFInvalidEncodingException newWithClass: isa]; } j += d; } assert(j == nlen); nstr[j] = 0; [self freeMemory: ustr]; [self freeMemory: *string]; *string = nstr; *length = nlen; } @implementation OFMutableString - setToCString: (const char*)str { size_t len; [self freeMemory: string]; len = strlen(str); switch (of_string_check_utf8(str, len)) { case 0: is_utf8 = NO; break; case 1: is_utf8 = YES; break; case -1: string = NULL; length = 0; is_utf8 = NO; @throw [OFInvalidEncodingException newWithClass: isa]; } length = len; string = [self allocMemoryWithSize: length + 1]; memcpy(string, str, length + 1); return self; } - appendCString: (const char*)str { size_t strlength; strlength = strlen(str); switch (of_string_check_utf8(str, strlength)) { case 1: is_utf8 = YES; break; case -1: @throw [OFInvalidEncodingException newWithClass: isa]; } string = [self resizeMemory: string toSize: length + strlength + 1]; memcpy(string + length, str, strlength + 1); length += strlength; return self; } - appendCString: (const char*)str withLength: (size_t)len { if (len > strlen(str)) @throw [OFOutOfRangeException newWithClass: isa]; switch (of_string_check_utf8(str, len)) { case 1: is_utf8 = YES; break; case -1: @throw [OFInvalidEncodingException newWithClass: isa]; } string = [self resizeMemory: string toSize: length + len + 1]; memcpy(string + length, str, len); length += len; string[length] = 0; return self; } - appendCStringWithoutUTF8Checking: (const char*)str { size_t strlength; strlength = strlen(str); string = [self resizeMemory: string toSize: length + strlength + 1]; memcpy(string + length, str, strlength + 1); length += strlength; return self; } - appendCStringWithoutUTF8Checking: (const char*)str length: (size_t)len { if (len > strlen(str)) @throw [OFOutOfRangeException newWithClass: isa]; string = [self resizeMemory: string toSize: length + len + 1]; memcpy(string + length, str, len); length += len; string[length] = 0; return self; } - appendString: (OFString*)str { [self appendCStringWithoutUTF8Checking: [str cString]]; if (str->is_utf8) is_utf8 = YES; return self; } - appendWithFormat: (OFString*)fmt, ... { id ret; va_list args; va_start(args, fmt); ret = [self appendWithFormat: fmt arguments: args]; va_end(args); return ret; } - appendWithFormat: (OFString*)fmt arguments: (va_list)args { char *t; if (fmt == NULL) @throw [OFInvalidFormatException newWithClass: isa]; if ((vasprintf(&t, [fmt cString], args)) == -1) /* * This is only the most likely error to happen. * Unfortunately, as errno isn't always thread-safe, there's * no good way for us to find out what really happened. */ @throw [OFOutOfMemoryException newWithClass: isa]; @try { [self appendCString: t]; } @finally { free(t); } return self; } - reverse { size_t i, j, len = length / 2; madvise(string, len, MADV_SEQUENTIAL); /* We reverse all bytes and restore UTF-8 later, if necessary */ for (i = 0, j = length - 1; i < len; i++, j--) { string[i] ^= string[j]; string[j] ^= string[i]; string[i] ^= string[j]; } if (!is_utf8) { madvise(string, len, MADV_NORMAL); return self; } for (i = 0; i < length; i++) { /* ASCII */ if (OF_LIKELY(!(string[i] & 0x80))) continue; /* A start byte can't happen first as we reversed everything */ if (OF_UNLIKELY(string[i] & 0x40)) { madvise(string, len, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 1 || !(string[i + 1] & 0x80))) { madvise(string, len, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Next byte is the start byte */ if (OF_LIKELY(string[i + 1] & 0x40)) { string[i] ^= string[i + 1]; string[i + 1] ^= string[i]; string[i] ^= string[i + 1]; i++; continue; } /* Second next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 2 || !(string[i + 2] & 0x80))) { madvise(string, len, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Second next byte is the start byte */ if (OF_LIKELY(string[i + 2] & 0x40)) { string[i] ^= string[i + 2]; string[i + 2] ^= string[i]; string[i] ^= string[i + 2]; i += 2; continue; } /* Third next byte must not be ASCII */ if (OF_UNLIKELY(length < i + 3 || !(string[i + 3] & 0x80))) { madvise(string, len, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } /* Third next byte is the start byte */ if (OF_LIKELY(string[i + 3] & 0x40)) { string[i] ^= string[i + 3]; string[i + 3] ^= string[i]; string[i] ^= string[i + 3]; string[i + 1] ^= string[i + 2]; string[i + 2] ^= string[i + 1]; string[i + 1] ^= string[i + 2]; i += 3; continue; } /* UTF-8 does not allow more than 4 bytes per character */ madvise(string, len, MADV_NORMAL); @throw [OFInvalidEncodingException newWithClass: isa]; } madvise(string, len, MADV_NORMAL); return self; } - upper { apply_table(self, isa, &string, &length, is_utf8, of_unicode_upper_table, OF_UNICODE_UPPER_TABLE_SIZE); return self; } - lower { apply_table(self, isa, &string, &length, is_utf8, of_unicode_lower_table, OF_UNICODE_LOWER_TABLE_SIZE); return self; } - removeCharactersFromIndex: (size_t)start toIndex: (size_t)end { if (is_utf8) { start = of_string_index_to_position(string, start, length); end = of_string_index_to_position(string, end, length); } if (start > end) @throw [OFInvalidArgumentException newWithClass: isa selector: _cmd]; if (end > length) @throw [OFOutOfRangeException newWithClass: isa]; memmove(string + start, string + end, length - end); length -= end - start; string[length] = 0; @try { string = [self resizeMemory: string toSize: length + 1]; } @catch (OFOutOfMemoryException *e) { /* We don't really care, as we only made it smaller */ [e dealloc]; } return self; } - removeCharactersInRange: (of_range_t)range { return [self removeCharactersFromIndex: range.start toIndex: range.start + range.length]; } - replaceOccurrencesOfString: (OFString*)str withString: (OFString*)repl { const char *str_c = [str cString]; const char *repl_c = [repl cString]; size_t str_len = [str cStringLength]; size_t repl_len = [repl cStringLength]; size_t i, last, tmp_len; char *tmp; if (str_len > length) return self; tmp = NULL; tmp_len = 0; for (i = 0, last = 0; i <= length - str_len; i++) { if (memcmp(string + i, str_c, str_len)) continue; @try { tmp = [self resizeMemory: tmp toSize: tmp_len + i - last + repl_len + 1]; } @catch (OFException *e) { [self freeMemory: tmp]; @throw e; } memcpy(tmp + tmp_len, string + last, i - last); memcpy(tmp + tmp_len + i - last, repl_c, repl_len); tmp_len += i - last + repl_len; i += str_len - 1; last = i + 1; } @try { tmp = [self resizeMemory: tmp toSize: tmp_len + length - last + 1]; } @catch (OFException *e) { [self freeMemory: tmp]; @throw e; } memcpy(tmp + tmp_len, string + last, length - last); tmp_len += length - last; tmp[tmp_len] = 0; [self freeMemory: string]; string = tmp; length = tmp_len; return self; } - removeLeadingWhitespaces { size_t i; for (i = 0; i < length; i++) if (string[i] != ' ' && string[i] != '\t' && string[i] != '\n' && string[i] != '\r') break; length -= i; memmove(string, string + i, length); string[length] = '\0'; @try { string = [self resizeMemory: string toSize: length + 1]; } @catch (OFOutOfMemoryException *e) { /* We don't really care, as we only made it smaller */ [e dealloc]; } return self; } - removeTrailingWhitespaces { size_t d; char *p; d = 0; for (p = string + length - 1; p >= string; p--) { if (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') break; *p = '\0'; d++; } length -= d; @try { string = [self resizeMemory: string toSize: length + 1]; } @catch (OFOutOfMemoryException *e) { /* We don't really care, as we only made it smaller */ [e dealloc]; } return self; } - removeLeadingAndTrailingWhitespaces { size_t d, i; char *p; d = 0; for (p = string + length - 1; p >= string; p--) { if (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\r') break; *p = '\0'; d++; } length -= d; for (i = 0; i < length; i++) if (string[i] != ' ' && string[i] != '\t' && string[i] != '\n' && string[i] != '\r') break; length -= i; memmove(string, string + i, length); string[length] = '\0'; @try { string = [self resizeMemory: string toSize: length + 1]; } @catch (OFOutOfMemoryException *e) { /* We don't really care, as we only made it smaller */ [e dealloc]; } return self; } - (id)copy { return [[OFString alloc] initWithString: self]; } @end