ObjFW  Artifact [2a34be912f]

Artifact 2a34be912f875be996843d73eed1441e5ba33f3dd4e33479db13b1e28889e14a:

  • File src/OFString.m — part of check-in [6dff0f5922] at 2017-01-07 02:34:39 on branch trunk — Always use "." for the decimal point

    This is achieved by replacing the locale's decimal point with "." after
    formatting and replacing "." with the locale's decimal point before
    parsing.

    To still use the decimal point from the locale for formatting, the new
    flag "," is introduced to formats. This is useful for just printing a
    string to the user that is not saved to a file or sent via a network.

    While this is an ugly hack, there is no better way to do this other than
    implementing the functionality of printf and strtod myself, as POSIX
    does not specify versions of these that take a locale as an argument.
    While this is a lot of work and error-prone, I will most likely end up
    doing this eventually.

    This commit also enables the locale in OFApplication to notice when
    things break. As a nice side effect, ofhttp now uses the locale's
    decimal point in its user interface. (user: js, size: 58336) [annotate] [blame] [check-ins using]


/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016
 *   Jonathan Schleifer <js@heap.zone>
 *
 * 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.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include <sys/stat.h>

#import "OFString.h"
#import "OFString_UTF8.h"
#import "OFString_UTF8+Private.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFDataArray.h"
#import "OFSystemInfo.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
#endif
#import "OFURL.h"
#ifdef OF_HAVE_SOCKETS
# import "OFHTTPClient.h"
# import "OFHTTPRequest.h"
# import "OFHTTPResponse.h"
#endif
#import "OFXMLElement.h"

#ifdef OF_HAVE_SOCKETS
# import "OFHTTPRequestFailedException.h"
#endif
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidEncodingException.h"
#import "OFInvalidFormatException.h"
#import "OFNotImplementedException.h"
#import "OFOpenItemFailedException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"

#import "of_asprintf.h"
#import "unicode.h"

/*
 * It seems strtod is buggy on Win32.
 * However, the MinGW version __strtod seems to be ok.
 */
#ifdef __MINGW32__
# define strtod __strtod
#endif

@interface OFString ()
- (size_t)OF_getCString: (char*)cString
	      maxLength: (size_t)maxLength
	       encoding: (of_string_encoding_t)encoding
		  lossy: (bool)lossy;
- (const char*)OF_cStringWithEncoding: (of_string_encoding_t)encoding
				lossy: (bool)lossy;
- (OFString*)OF_JSONRepresentationWithOptions: (int)options
					depth: (size_t)depth;
@end

extern bool of_unicode_to_iso_8859_15(const of_unichar_t*, uint8_t*, size_t,
    bool);
extern bool of_unicode_to_windows_1252(const of_unichar_t*, uint8_t*, size_t,
    bool);
extern bool of_unicode_to_codepage_437(const of_unichar_t*, uint8_t*, size_t,
    bool);

/* References for static linking */
void
_references_to_categories_of_OFString(void)
{
	_OFString_CryptoHashing_reference = 1;
	_OFString_JSONValue_reference = 1;
	_OFString_Serialization_reference = 1;
	_OFString_URLEncoding_reference = 1;
	_OFString_XMLEscaping_reference = 1;
	_OFString_XMLUnescaping_reference = 1;
}

void
_reference_to_OFConstantString(void)
{
	[OFConstantString class];
}

size_t
of_string_utf8_encode(of_unichar_t character, char *buffer)
{
	size_t i = 0;

	if (character < 0x80) {
		buffer[i] = character;
		return 1;
	} else if (character < 0x800) {
		buffer[i++] = 0xC0 | (character >> 6);
		buffer[i] = 0x80 | (character & 0x3F);
		return 2;
	} else if (character < 0x10000) {
		buffer[i++] = 0xE0 | (character >> 12);
		buffer[i++] = 0x80 | (character >> 6 & 0x3F);
		buffer[i] = 0x80 | (character & 0x3F);
		return 3;
	} else if (character < 0x110000) {
		buffer[i++] = 0xF0 | (character >> 18);
		buffer[i++] = 0x80 | (character >> 12 & 0x3F);
		buffer[i++] = 0x80 | (character >> 6 & 0x3F);
		buffer[i] = 0x80 | (character & 0x3F);
		return 4;
	}

	return 0;
}

ssize_t
of_string_utf8_decode(const char *buffer_, size_t length, of_unichar_t *ret)
{
	const uint8_t *buffer = (const uint8_t*)buffer_;

	if (!(*buffer & 0x80)) {
		*ret = buffer[0];
		return 1;
	}

	if ((*buffer & 0xE0) == 0xC0) {
		if OF_UNLIKELY (length < 2)
			return -2;

		if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80)
			return 0;

		*ret = ((buffer[0] & 0x1F) << 6) | (buffer[1] & 0x3F);
		return 2;
	}

	if ((*buffer & 0xF0) == 0xE0) {
		if OF_UNLIKELY (length < 3)
			return -3;

		if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80 ||
		    (buffer[2] & 0xC0) != 0x80)
			return 0;

		*ret = ((buffer[0] & 0x0F) << 12) | ((buffer[1] & 0x3F) << 6) |
		    (buffer[2] & 0x3F);
		return 3;
	}

	if ((*buffer & 0xF8) == 0xF0) {
		if OF_UNLIKELY (length < 4)
			return -4;

		if OF_UNLIKELY ((buffer[1] & 0xC0) != 0x80 ||
		    (buffer[2] & 0xC0) != 0x80 || (buffer[3] & 0xC0) != 0x80)
			return 0;

		*ret = ((buffer[0] & 0x07) << 18) | ((buffer[1] & 0x3F) << 12) |
		    ((buffer[2] & 0x3F) << 6) | (buffer[3] & 0x3F);
		return 4;
	}

	return 0;
}

size_t
of_string_utf16_length(const of_char16_t *string)
{
	size_t length = 0;

	while (*string++ != 0)
		length++;

	return length;
}

size_t
of_string_utf32_length(const of_char32_t *string)
{
	size_t length = 0;

	while (*string++ != 0)
		length++;

	return length;
}

static OFString*
standardizePath(OFArray *components, OFString *currentDirectory,
    OFString *parentDirectory, OFString *joinString)
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableArray *array;
	OFString *ret;
	bool done = false, startsWithEmpty, endsWithEmpty;

	array = [[components mutableCopy] autorelease];

	if ((startsWithEmpty = [[array firstObject] isEqual: @""]))
		[array removeObjectAtIndex: 0];
	endsWithEmpty = [[array lastObject] isEqual: @""];

	while (!done) {
		size_t length = [array count];

		done = true;

		for (size_t i = 0; i < length; i++) {
			id object = [array objectAtIndex: i];
			id parent;

			if (i > 0)
				parent = [array objectAtIndex: i - 1];
			else
				parent = nil;

			if ([object isEqual: currentDirectory] ||
			   [object length] == 0) {
				[array removeObjectAtIndex: i];

				done = false;
				break;
			}

			if ([object isEqual: parentDirectory] &&
			    parent != nil &&
			    ![parent isEqual: parentDirectory]) {
				[array removeObjectsInRange:
				    of_range(i - 1, 2)];

				done = false;
				break;
			}
		}
	}

	if (startsWithEmpty)
		[array insertObject: @""
			    atIndex: 0];
	if (endsWithEmpty)
		[array addObject: @""];

	ret = [[array componentsJoinedByString: joinString] retain];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}

static struct {
	Class isa;
} placeholder;

@interface OFString_placeholder: OFString
@end

@implementation OFString_placeholder
- init
{
	return (id)[[OFString_UTF8 alloc] init];
}

- initWithUTF8String: (const char*)UTF8String
{
	id string;
	size_t length;
	void *storage;

	length = strlen(UTF8String);
	string = of_alloc_object([OFString_UTF8 class],
	    length + 1, 1, &storage);

	return (id)[string OF_initWithUTF8String: UTF8String
					  length: length
					 storage: storage];
}

- initWithUTF8String: (const char*)UTF8String
	      length: (size_t)UTF8StringLength
{
	id string;
	void *storage;

	string = of_alloc_object([OFString_UTF8 class],
	    UTF8StringLength + 1, 1, &storage);

	return (id)[string OF_initWithUTF8String: UTF8String
					  length: UTF8StringLength
					 storage: storage];
}

- initWithUTF8StringNoCopy: (char*)UTF8String
	      freeWhenDone: (bool)freeWhenDone
{
	return (id)[[OFString_UTF8 alloc]
	    initWithUTF8StringNoCopy: UTF8String
			freeWhenDone: freeWhenDone];
}

- initWithCString: (const char*)cString
	 encoding: (of_string_encoding_t)encoding
{
	if (encoding == OF_STRING_ENCODING_UTF_8) {
		id string;
		size_t length;
		void *storage;

		length = strlen(cString);
		string = of_alloc_object([OFString_UTF8 class],
		    length + 1, 1, &storage);

		return (id)[string OF_initWithUTF8String: cString
						  length: length
						 storage: storage];
	}

	return (id)[[OFString_UTF8 alloc] initWithCString: cString
						 encoding: encoding];
}

- initWithCString: (const char*)cString
	 encoding: (of_string_encoding_t)encoding
	   length: (size_t)cStringLength
{
	if (encoding == OF_STRING_ENCODING_UTF_8) {
		id string;
		void *storage;

		string = of_alloc_object([OFString_UTF8 class],
		    cStringLength + 1, 1, &storage);

		return (id)[string OF_initWithUTF8String: cString
						  length: cStringLength
						 storage: storage];
	}

	return (id)[[OFString_UTF8 alloc] initWithCString: cString
						 encoding: encoding
						   length: cStringLength];
}

- initWithString: (OFString*)string
{
	return (id)[[OFString_UTF8 alloc] initWithString: string];
}

- initWithCharacters: (const of_unichar_t*)string
	      length: (size_t)length
{
	return (id)[[OFString_UTF8 alloc] initWithCharacters: string
						      length: length];
}

- initWithUTF16String: (const of_char16_t*)string
{
	return (id)[[OFString_UTF8 alloc] initWithUTF16String: string];
}

- initWithUTF16String: (const of_char16_t*)string
	       length: (size_t)length
{
	return (id)[[OFString_UTF8 alloc] initWithUTF16String: string
						       length: length];
}

- initWithUTF16String: (const of_char16_t*)string
	    byteOrder: (of_byte_order_t)byteOrder
{
	return (id)[[OFString_UTF8 alloc] initWithUTF16String: string
						    byteOrder: byteOrder];
}

- initWithUTF16String: (const of_char16_t*)string
	       length: (size_t)length
	    byteOrder: (of_byte_order_t)byteOrder
{
	return (id)[[OFString_UTF8 alloc] initWithUTF16String: string
						       length: length
						    byteOrder: byteOrder];
}

- initWithUTF32String: (const of_char32_t*)string
{
	return (id)[[OFString_UTF8 alloc] initWithUTF32String: string];
}

- initWithUTF32String: (const of_char32_t*)string
	       length: (size_t)length
{
	return (id)[[OFString_UTF8 alloc] initWithUTF32String: string
						       length: length];
}

- initWithUTF32String: (const of_char32_t*)string
	    byteOrder: (of_byte_order_t)byteOrder
{
	return (id)[[OFString_UTF8 alloc] initWithUTF32String: string
						    byteOrder: byteOrder];
}

- initWithUTF32String: (const of_char32_t*)string
	       length: (size_t)length
	    byteOrder: (of_byte_order_t)byteOrder
{
	return (id)[[OFString_UTF8 alloc] initWithUTF32String: string
						       length: length
						    byteOrder: byteOrder];
}

- initWithFormat: (OFConstantString*)format, ...
{
	id ret;
	va_list arguments;

	va_start(arguments, format);
	ret = [[OFString_UTF8 alloc] initWithFormat: format
					  arguments: arguments];
	va_end(arguments);

	return ret;
}

- initWithFormat: (OFConstantString*)format
       arguments: (va_list)arguments
{
	return (id)[[OFString_UTF8 alloc] initWithFormat: format
					       arguments: arguments];
}

#ifdef OF_HAVE_FILES
- initWithContentsOfFile: (OFString*)path
{
	return (id)[[OFString_UTF8 alloc] initWithContentsOfFile: path];
}

- initWithContentsOfFile: (OFString*)path
		encoding: (of_string_encoding_t)encoding
{
	return (id)[[OFString_UTF8 alloc] initWithContentsOfFile: path
							encoding: encoding];
}
#endif

#if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS)
- initWithContentsOfURL: (OFURL*)URL
{
	return (id)[[OFString_UTF8 alloc] initWithContentsOfURL: URL];
}

- initWithContentsOfURL: (OFURL*)URL
	       encoding: (of_string_encoding_t)encoding
{
	return (id)[[OFString_UTF8 alloc] initWithContentsOfURL: URL
						       encoding: encoding];
}
#endif

- initWithSerialization: (OFXMLElement*)element
{
	return (id)[[OFString_UTF8 alloc] initWithSerialization: element];
}

- retain
{
	return self;
}

- autorelease
{
	return self;
}

- (void)release
{
}

- (void)dealloc
{
	OF_DEALLOC_UNSUPPORTED
}
@end

@implementation OFString
+ (void)initialize
{
	if (self == [OFString class])
		placeholder.isa = [OFString_placeholder class];
}

+ alloc
{
	if (self == [OFString class])
		return (id)&placeholder;

	return [super alloc];
}

+ (instancetype)string
{
	return [[[self alloc] init] autorelease];
}

+ (instancetype)stringWithUTF8String: (const char*)UTF8String
{
	return [[[self alloc] initWithUTF8String: UTF8String] autorelease];
}

+ (instancetype)stringWithUTF8String: (const char*)UTF8String
			      length: (size_t)UTF8StringLength
{
	return [[[self alloc]
	    initWithUTF8String: UTF8String
			length: UTF8StringLength] autorelease];
}

+ (instancetype)stringWithUTF8StringNoCopy: (char*)UTF8String
			      freeWhenDone: (bool)freeWhenDone
{
	return [[[self alloc]
	    initWithUTF8StringNoCopy: UTF8String
			freeWhenDone: freeWhenDone] autorelease];
}

+ (instancetype)stringWithCString: (const char*)cString
			 encoding: (of_string_encoding_t)encoding
{
	return [[[self alloc] initWithCString: cString
				     encoding: encoding] autorelease];
}

+ (instancetype)stringWithCString: (const char*)cString
			 encoding: (of_string_encoding_t)encoding
			   length: (size_t)cStringLength
{
	return [[[self alloc] initWithCString: cString
				     encoding: encoding
				       length: cStringLength] autorelease];
}

+ (instancetype)stringWithString: (OFString*)string
{
	return [[[self alloc] initWithString: string] autorelease];
}

+ (instancetype)stringWithCharacters: (const of_unichar_t*)string
			      length: (size_t)length
{
	return [[[self alloc] initWithCharacters: string
					  length: length] autorelease];
}

+ (instancetype)stringWithUTF16String: (const of_char16_t*)string
{
	return [[[self alloc] initWithUTF16String: string] autorelease];
}

+ (instancetype)stringWithUTF16String: (const of_char16_t*)string
			       length: (size_t)length
{
	return [[[self alloc] initWithUTF16String: string
					   length: length] autorelease];
}

+ (instancetype)stringWithUTF16String: (const of_char16_t*)string
			    byteOrder: (of_byte_order_t)byteOrder
{
	return [[[self alloc] initWithUTF16String: string
					byteOrder: byteOrder] autorelease];
}

+ (instancetype)stringWithUTF16String: (const of_char16_t*)string
			       length: (size_t)length
			    byteOrder: (of_byte_order_t)byteOrder
{
	return [[[self alloc] initWithUTF16String: string
					   length: length
					byteOrder: byteOrder] autorelease];
}

+ (instancetype)stringWithUTF32String: (const of_char32_t*)string
{
	return [[[self alloc] initWithUTF32String: string] autorelease];
}

+ (instancetype)stringWithUTF32String: (const of_char32_t*)string
			       length: (size_t)length
{
	return [[[self alloc] initWithUTF32String: string
					   length: length] autorelease];
}

+ (instancetype)stringWithUTF32String: (const of_char32_t*)string
			    byteOrder: (of_byte_order_t)byteOrder
{
	return [[[self alloc] initWithUTF32String: string
					byteOrder: byteOrder] autorelease];
}

+ (instancetype)stringWithUTF32String: (const of_char32_t*)string
			       length: (size_t)length
			    byteOrder: (of_byte_order_t)byteOrder
{
	return [[[self alloc] initWithUTF32String: string
					   length: length
					byteOrder: byteOrder] autorelease];
}

+ (instancetype)stringWithFormat: (OFConstantString*)format, ...
{
	id ret;
	va_list arguments;

	va_start(arguments, format);
	ret = [[[self alloc] initWithFormat: format
				  arguments: arguments] autorelease];
	va_end(arguments);

	return ret;
}

#ifdef OF_HAVE_FILES
+ (instancetype)stringWithContentsOfFile: (OFString*)path
{
	return [[[self alloc] initWithContentsOfFile: path] autorelease];
}

+ (instancetype)stringWithContentsOfFile: (OFString*)path
				encoding: (of_string_encoding_t)encoding
{
	return [[[self alloc] initWithContentsOfFile: path
					    encoding: encoding] autorelease];
}
#endif

#if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS)
+ (instancetype)stringWithContentsOfURL: (OFURL*)URL
{
	return [[[self alloc] initWithContentsOfURL: URL] autorelease];
}

+ (instancetype)stringWithContentsOfURL: (OFURL*)URL
			       encoding: (of_string_encoding_t)encoding
{
	return [[[self alloc] initWithContentsOfURL: URL
					   encoding: encoding] autorelease];
}
#endif

+ (OFString*)pathWithComponents: (OFArray*)components
{
	OFMutableString *ret = [OFMutableString string];
	bool first = true;

	for (OFString *component in components) {
		if (!first)
			[ret appendString: OF_PATH_DELIMITER_STRING];

		[ret appendString: component];

		first = false;
	}

	return ret;
}

- init
{
	if (object_getClass(self) == [OFString class]) {
		@try {
			[self doesNotRecognizeSelector: _cmd];
		} @catch (id e) {
			[self release];
			@throw e;
		}

		abort();
	}

	return [super init];
}

- initWithUTF8String: (const char*)UTF8String
{
	return [self initWithCString: UTF8String
			    encoding: OF_STRING_ENCODING_UTF_8
			      length: strlen(UTF8String)];
}

- initWithUTF8String: (const char*)UTF8String
	      length: (size_t)UTF8StringLength
{
	return [self initWithCString: UTF8String
			    encoding: OF_STRING_ENCODING_UTF_8
			      length: UTF8StringLength];
}

- initWithUTF8StringNoCopy: (char*)UTF8String
	      freeWhenDone: (bool)freeWhenDone
{
	return [self initWithUTF8String: UTF8String];
}

- initWithCString: (const char*)cString
	 encoding: (of_string_encoding_t)encoding
{
	return [self initWithCString: cString
			    encoding: encoding
			      length: strlen(cString)];
}

- initWithCString: (const char*)cString
	 encoding: (of_string_encoding_t)encoding
	   length: (size_t)cStringLength
{
	OF_INVALID_INIT_METHOD
}

- initWithString: (OFString*)string
{
	OF_INVALID_INIT_METHOD
}

- initWithCharacters: (const of_unichar_t*)string
	      length: (size_t)length
{
	OF_INVALID_INIT_METHOD
}

- initWithUTF16String: (const of_char16_t*)string
{
	return [self initWithUTF16String: string
				  length: of_string_utf16_length(string)
			       byteOrder: OF_BYTE_ORDER_NATIVE];
}

- initWithUTF16String: (const of_char16_t*)string
	       length: (size_t)length
{
	return [self initWithUTF16String: string
				  length: length
			       byteOrder: OF_BYTE_ORDER_NATIVE];
}

- initWithUTF16String: (const of_char16_t*)string
	    byteOrder: (of_byte_order_t)byteOrder
{
	return [self initWithUTF16String: string
				  length: of_string_utf16_length(string)
			       byteOrder: byteOrder];
}

- initWithUTF16String: (const of_char16_t*)string
	       length: (size_t)length
	    byteOrder: (of_byte_order_t)byteOrder
{
	OF_INVALID_INIT_METHOD
}

- initWithUTF32String: (const of_char32_t*)string
{
	return [self initWithUTF32String: string
				  length: of_string_utf32_length(string)
			       byteOrder: OF_BYTE_ORDER_NATIVE];
}

- initWithUTF32String: (const of_char32_t*)string
	       length: (size_t)length
{
	return [self initWithUTF32String: string
				  length: length
			       byteOrder: OF_BYTE_ORDER_NATIVE];
}

- initWithUTF32String: (const of_char32_t*)string
	    byteOrder: (of_byte_order_t)byteOrder
{
	return [self initWithUTF32String: string
				  length: of_string_utf32_length(string)
			       byteOrder: byteOrder];
}

- initWithUTF32String: (const of_char32_t*)string
	       length: (size_t)length
	    byteOrder: (of_byte_order_t)byteOrder
{
	OF_INVALID_INIT_METHOD
}

- initWithFormat: (OFConstantString*)format, ...
{
	id ret;
	va_list arguments;

	va_start(arguments, format);
	ret = [self initWithFormat: format
			 arguments: arguments];
	va_end(arguments);

	return ret;
}

- initWithFormat: (OFConstantString*)format
       arguments: (va_list)arguments
{
	OF_INVALID_INIT_METHOD
}

#ifdef OF_HAVE_FILES
- initWithContentsOfFile: (OFString*)path
{
	return [self initWithContentsOfFile: path
				   encoding: OF_STRING_ENCODING_UTF_8];
}

- initWithContentsOfFile: (OFString*)path
		encoding: (of_string_encoding_t)encoding
{
	char *tmp;
	of_stat_t st;

	@try {
		OFFile *file;

		/* Make sure the file system is initialized */
		[OFFile class];

		if (of_stat(path, &st) != 0)
			@throw [OFOpenItemFailedException
			    exceptionWithPath: path
					 mode: @"rb"
					errNo: errno];

		if (sizeof(of_offset_t) > sizeof(size_t) &&
		    st.st_size > (of_offset_t)SIZE_MAX)
			@throw [OFOutOfRangeException exception];

		file = [[OFFile alloc] initWithPath: path
					       mode: @"rb"];

		@try {
			tmp = [self allocMemoryWithSize: (size_t)st.st_size];

			[file readIntoBuffer: tmp
				 exactLength: (size_t)st.st_size];
		} @finally {
			[file release];
		}
	} @catch (id e) {
		[self release];
		@throw e;
	}

	self = [self initWithCString: tmp
			    encoding: encoding
			      length: (size_t)st.st_size];
	[self freeMemory: tmp];

	return self;
}
#endif

#if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS)
- initWithContentsOfURL: (OFURL*)URL
{
	return [self initWithContentsOfURL: URL
				  encoding: OF_STRING_ENCODING_AUTODETECT];
}

- initWithContentsOfURL: (OFURL*)URL
	       encoding: (of_string_encoding_t)encoding
{
	void *pool;
	OFString *scheme;
# if defined(OF_HAVE_FILES) || defined(OF_HAVE_SOCKETS)
	Class c = [self class];
# endif

	[self release];

	pool = objc_autoreleasePoolPush();

	scheme = [URL scheme];

# ifdef OF_HAVE_FILES
	if ([scheme isEqual: @"file"]) {
		if (encoding == OF_STRING_ENCODING_AUTODETECT)
			encoding = OF_STRING_ENCODING_UTF_8;

		self = [[c alloc] initWithContentsOfFile: [URL path]
						encoding: encoding];
	} else
# endif
# ifdef OF_HAVE_SOCKETS
	if ([scheme isEqual: @"http"] || [scheme isEqual: @"https"]) {
		OFHTTPClient *client = [OFHTTPClient client];
		OFHTTPRequest *request = [OFHTTPRequest requestWithURL: URL];
		OFHTTPResponse *response = [client performRequest: request];
		OFDictionary *headers;
		OFString *contentType, *contentLength;
		OFDataArray *data;

		if ([response statusCode] != 200)
			@throw [OFHTTPRequestFailedException
			    exceptionWithRequest: request
					response: response];

		headers = [response headers];

		if (encoding == OF_STRING_ENCODING_AUTODETECT &&
		    (contentType = [headers
		    objectForKey: @"Content-Type"]) != nil) {
			contentType = [contentType lowercaseString];

			if ([contentType hasSuffix: @"charset=utf-8"])
				encoding = OF_STRING_ENCODING_UTF_8;
			if ([contentType hasSuffix: @"charset=iso-8859-1"])
				encoding = OF_STRING_ENCODING_ISO_8859_1;
			if ([contentType hasSuffix: @"charset=iso-8859-15"])
				encoding = OF_STRING_ENCODING_ISO_8859_15;
			if ([contentType hasSuffix: @"charset=windows-1252"])
				encoding = OF_STRING_ENCODING_WINDOWS_1252;
		}

		if (encoding == OF_STRING_ENCODING_AUTODETECT)
			encoding = OF_STRING_ENCODING_UTF_8;

		data = [response readDataArrayTillEndOfStream];

		if ((contentLength = [headers
		    objectForKey: @"Content-Length"]) != nil)
			if ([data count] !=
			    (size_t)[contentLength decimalValue])
				@throw [OFTruncatedDataException exception];

		self = [[c alloc] initWithCString: (char*)[data items]
					 encoding: encoding
					   length: [data count]];
	} else
# endif
		@throw [OFUnsupportedProtocolException exception];

	objc_autoreleasePoolPop(pool);

	return self;
}
#endif

- initWithSerialization: (OFXMLElement*)element
{
	@try {
		void *pool = objc_autoreleasePoolPush();

		if (![[element namespace] isEqual: OF_SERIALIZATION_NS])
			@throw [OFInvalidArgumentException exception];

		if ([self isKindOfClass: [OFMutableString class]]) {
			if (![[element name] isEqual: @"OFMutableString"])
				@throw [OFInvalidArgumentException exception];
		} else {
			if (![[element name] isEqual: @"OFString"])
				@throw [OFInvalidArgumentException exception];
		}

		self = [self initWithString: [element stringValue]];

		objc_autoreleasePoolPop(pool);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (size_t)OF_getCString: (char*)cString
	      maxLength: (size_t)maxLength
	       encoding: (of_string_encoding_t)encoding
		  lossy: (bool)lossy
{
	const of_unichar_t *characters = [self characters];
	size_t i, length = [self length];

	switch (encoding) {
	case OF_STRING_ENCODING_UTF_8:;
		size_t j = 0;

		for (i = 0; i < length; i++) {
			char buffer[4];
			size_t len = of_string_utf8_encode(characters[i],
			    buffer);

			/*
			 * Check for one more than the current index, as we
			 * need one for the terminating zero.
			 */
			if (j + len >= maxLength)
				@throw [OFOutOfRangeException exception];

			switch (len) {
			case 1:
				cString[j++] = buffer[0];

				break;
			case 2:
			case 3:
			case 4:
				memcpy(cString + j, buffer, len);
				j += len;

				break;
			default:
				@throw [OFInvalidEncodingException exception];

				break;
			}
		}

		cString[j] = '\0';

		return j;
	case OF_STRING_ENCODING_ASCII:
		if (length + 1 > maxLength)
			@throw [OFOutOfRangeException exception];

		for (i = 0; i < length; i++) {
			if OF_UNLIKELY (characters[i] > 0x80) {
				if (lossy)
					cString[i] = '?';
				else
					@throw [OFInvalidEncodingException
					    exception];
			} else
				cString[i] = (char)characters[i];
		}

		cString[i] = '\0';

		return length;
	case OF_STRING_ENCODING_ISO_8859_1:
		if (length + 1 > maxLength)
			@throw [OFOutOfRangeException exception];

		for (i = 0; i < length; i++) {
			if OF_UNLIKELY (characters[i] > 0xFF) {
				if (lossy)
					cString[i] = '?';
				else
					@throw [OFInvalidEncodingException
					    exception];
			} else
				cString[i] = (uint8_t)characters[i];
		}

		cString[i] = '\0';

		return length;
	case OF_STRING_ENCODING_ISO_8859_15:
		if (length + 1 > maxLength)
			@throw [OFOutOfRangeException exception];

		if (!of_unicode_to_iso_8859_15(characters, (uint8_t*)cString,
		    length, lossy))
			@throw [OFInvalidEncodingException exception];

		cString[length] = '\0';

		return length;
	case OF_STRING_ENCODING_WINDOWS_1252:
		if (length + 1 > maxLength)
			@throw [OFOutOfRangeException exception];

		if (!of_unicode_to_windows_1252(characters, (uint8_t*)cString,
		    length, lossy))
			@throw [OFInvalidEncodingException exception];

		cString[length] = '\0';

		return length;
	case OF_STRING_ENCODING_CODEPAGE_437:
		if (length + 1 > maxLength)
			@throw [OFOutOfRangeException exception];

		if (!of_unicode_to_codepage_437(characters, (uint8_t*)cString,
		    length, lossy))
			@throw [OFInvalidEncodingException exception];

		cString[length] = '\0';

		return length;
	default:
		@throw [OFNotImplementedException exceptionWithSelector: _cmd
								 object: self];
	}
}

- (size_t)getCString: (char*)cString
	   maxLength: (size_t)maxLength
	    encoding: (of_string_encoding_t)encoding
{
	return [self OF_getCString: cString
			 maxLength: maxLength
			  encoding: encoding
			     lossy: false];
}

- (size_t)getLossyCString: (char*)cString
		maxLength: (size_t)maxLength
		 encoding: (of_string_encoding_t)encoding
{
	return [self OF_getCString: cString
			 maxLength: maxLength
			  encoding: encoding
			     lossy: true];
}

- (const char*)OF_cStringWithEncoding: (of_string_encoding_t)encoding
				lossy: (bool)lossy
{
	OFObject *object = [[[OFObject alloc] init] autorelease];
	size_t length = [self length];
	char *cString;

	switch (encoding) {
	case OF_STRING_ENCODING_UTF_8:;
		size_t cStringLength;

		cString = [object allocMemoryWithSize: (length * 4) + 1];

		cStringLength = [self OF_getCString: cString
					  maxLength: (length * 4) + 1
					   encoding: OF_STRING_ENCODING_UTF_8
					      lossy: lossy];

		@try {
			cString = [object resizeMemory: cString
						  size: cStringLength + 1];
		} @catch (OFOutOfMemoryException *e) {
			/* We don't care, as we only tried to make it smaller */
		}

		break;
	case OF_STRING_ENCODING_ASCII:
	case OF_STRING_ENCODING_ISO_8859_1:
	case OF_STRING_ENCODING_ISO_8859_15:
	case OF_STRING_ENCODING_WINDOWS_1252:
	case OF_STRING_ENCODING_CODEPAGE_437:
		cString = [object allocMemoryWithSize: length + 1];

		[self OF_getCString: cString
			  maxLength: length + 1
			   encoding: encoding
			      lossy: lossy];

		break;
	default:
		@throw [OFInvalidEncodingException exception];
	}

	return cString;
}

- (const char*)cStringWithEncoding: (of_string_encoding_t)encoding
{
	return [self OF_cStringWithEncoding: encoding
				      lossy: false];
}

- (const char*)lossyCStringWithEncoding: (of_string_encoding_t)encoding
{
	return [self OF_cStringWithEncoding: encoding
				      lossy: true];
}

- (const char*)UTF8String
{
	return [self cStringWithEncoding: OF_STRING_ENCODING_UTF_8];
}

- (size_t)length
{
	OF_UNRECOGNIZED_SELECTOR
}

- (size_t)cStringLengthWithEncoding: (of_string_encoding_t)encoding
{
	switch (encoding) {
	case OF_STRING_ENCODING_UTF_8:;
		const of_unichar_t *characters;
		size_t length, UTF8StringLength = 0;

		characters = [self characters];
		length = [self length];

		for (size_t i = 0; i < length; i++) {
			char buffer[4];
			size_t len = of_string_utf8_encode(characters[i],
			    buffer);

			if (len == 0)
				@throw [OFInvalidEncodingException exception];

			UTF8StringLength += len;
		}

		return UTF8StringLength;
	case OF_STRING_ENCODING_ASCII:
	case OF_STRING_ENCODING_ISO_8859_1:
	case OF_STRING_ENCODING_ISO_8859_15:
	case OF_STRING_ENCODING_WINDOWS_1252:
	case OF_STRING_ENCODING_CODEPAGE_437:
		return [self length];
	default:
		@throw [OFInvalidEncodingException exception];
	}
}

- (size_t)UTF8StringLength
{
	return [self cStringLengthWithEncoding: OF_STRING_ENCODING_UTF_8];
}

- (of_unichar_t)characterAtIndex: (size_t)index
{
	OF_UNRECOGNIZED_SELECTOR
}

- (void)getCharacters: (of_unichar_t*)buffer
	      inRange: (of_range_t)range
{
	for (size_t i = 0; i < range.length; i++)
		buffer[i] = [self characterAtIndex: range.location + i];
}

- (bool)isEqual: (id)object
{
	void *pool;
	OFString *otherString;
	const of_unichar_t *characters, *otherCharacters;
	size_t length;

	if (object == self)
		return true;

	if (![object isKindOfClass: [OFString class]])
		return false;

	otherString = object;
	length = [self length];

	if ([otherString length] != length)
		return false;

	pool = objc_autoreleasePoolPush();

	characters = [self characters];
	otherCharacters = [otherString characters];

	if (memcmp(characters, otherCharacters,
	    length * sizeof(of_unichar_t)) != 0) {
		objc_autoreleasePoolPop(pool);
		return false;
	}

	objc_autoreleasePoolPop(pool);

	return true;
}

- copy
{
	return [self retain];
}

- mutableCopy
{
	return [[OFMutableString alloc] initWithString: self];
}

- (of_comparison_result_t)compare: (id <OFComparing>)object
{
	void *pool;
	OFString *otherString;
	const of_unichar_t *characters, *otherCharacters;
	size_t minimumLength;

	if (object == self)
		return OF_ORDERED_SAME;

	if (![object isKindOfClass: [OFString class]])
		@throw [OFInvalidArgumentException exception];

	otherString = (OFString*)object;
	minimumLength = ([self length] > [otherString length]
	    ? [otherString length] : [self length]);

	pool = objc_autoreleasePoolPush();

	characters = [self characters];
	otherCharacters = [otherString characters];

	for (size_t i = 0; i < minimumLength; i++) {
		if (characters[i] > otherCharacters[i]) {
			objc_autoreleasePoolPop(pool);
			return OF_ORDERED_DESCENDING;
		}

		if (characters[i] < otherCharacters[i]) {
			objc_autoreleasePoolPop(pool);
			return OF_ORDERED_ASCENDING;
		}
	}

	objc_autoreleasePoolPop(pool);

	if ([self length] > [otherString length])
		return OF_ORDERED_DESCENDING;
	if ([self length] < [otherString length])
		return OF_ORDERED_ASCENDING;

	return OF_ORDERED_SAME;
}

- (of_comparison_result_t)caseInsensitiveCompare: (OFString*)otherString
{
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters, *otherCharacters;
	size_t length, otherLength, minimumLength;

	if (otherString == self)
		return OF_ORDERED_SAME;

	characters = [self characters];
	otherCharacters = [otherString characters];
	length = [self length];
	otherLength = [otherString length];

	minimumLength = (length > otherLength ? otherLength : length);

	for (size_t i = 0; i < minimumLength; i++) {
		of_unichar_t c = characters[i];
		of_unichar_t oc = otherCharacters[i];

#ifdef OF_HAVE_UNICODE_TABLES
		if (c >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) {
			of_unichar_t tc =
			    of_unicode_casefolding_table[c >> 8][c & 0xFF];

			if (tc)
				c = tc;
		}
		if (oc >> 8 < OF_UNICODE_CASEFOLDING_TABLE_SIZE) {
			of_unichar_t tc =
			    of_unicode_casefolding_table[oc >> 8][oc & 0xFF];

			if (tc)
				oc = tc;
		}
#else
		c = of_ascii_toupper(c);
		oc = of_ascii_toupper(oc);
#endif

		if (c > oc) {
			objc_autoreleasePoolPop(pool);
			return OF_ORDERED_DESCENDING;
		}
		if (c < oc) {
			objc_autoreleasePoolPop(pool);
			return OF_ORDERED_ASCENDING;
		}
	}

	objc_autoreleasePoolPop(pool);

	if (length > otherLength)
		return OF_ORDERED_DESCENDING;
	if (length < otherLength)
		return OF_ORDERED_ASCENDING;

	return OF_ORDERED_SAME;
}

- (uint32_t)hash
{
	const of_unichar_t *characters = [self characters];
	size_t length = [self length];
	uint32_t hash;

	OF_HASH_INIT(hash);

	for (size_t i = 0; i < length; i++) {
		const of_unichar_t c = characters[i];

		OF_HASH_ADD(hash, (c & 0xFF0000) >> 16);
		OF_HASH_ADD(hash, (c & 0x00FF00) >>  8);
		OF_HASH_ADD(hash,  c & 0x0000FF);
	}

	OF_HASH_FINALIZE(hash);

	return hash;
}

- (OFString*)description
{
	return [[self copy] autorelease];
}

- (OFXMLElement*)XMLElementBySerializing
{
	void *pool = objc_autoreleasePoolPush();
	OFXMLElement *element;
	OFString *className;

	if ([self isKindOfClass: [OFMutableString class]])
		className = @"OFMutableString";
	else
		className = @"OFString";

	element = [OFXMLElement elementWithName: className
				      namespace: OF_SERIALIZATION_NS
				    stringValue: self];

	[element retain];

	objc_autoreleasePoolPop(pool);

	return [element autorelease];
}

- (OFString*)JSONRepresentation
{
	return [self OF_JSONRepresentationWithOptions: 0
						depth: 0];
}

- (OFString*)JSONRepresentationWithOptions: (int)options
{
	return [self OF_JSONRepresentationWithOptions: options
						depth: 0];
}

- (OFString*)OF_JSONRepresentationWithOptions: (int)options
					depth: (size_t)depth
{
	OFMutableString *JSON = [[self mutableCopy] autorelease];

	/* FIXME: This is slow! Write it in pure C! */
	[JSON replaceOccurrencesOfString: @"\\"
			      withString: @"\\\\"];
	[JSON replaceOccurrencesOfString: @"\""
			      withString: @"\\\""];
	[JSON replaceOccurrencesOfString: @"\b"
			      withString: @"\\b"];
	[JSON replaceOccurrencesOfString: @"\f"
			      withString: @"\\f"];
	[JSON replaceOccurrencesOfString: @"\r"
			      withString: @"\\r"];
	[JSON replaceOccurrencesOfString: @"\t"
			      withString: @"\\t"];

	if (options & OF_JSON_REPRESENTATION_JSON5) {
		[JSON replaceOccurrencesOfString: @"\n"
				      withString: @"\\\n"];

		if (options & OF_JSON_REPRESENTATION_IDENTIFIER) {
			const char *cString = [self UTF8String];

			if ((!isalpha((int)cString[0]) && cString[0] != '_' &&
			    cString[0] != '$') ||
			    strpbrk(cString, " \n\r\t\b\f\\\"'") != NULL) {
				[JSON prependString: @"\""];
				[JSON appendString: @"\""];
			}
		} else {
			[JSON prependString: @"\""];
			[JSON appendString: @"\""];
		}
	} else {
		[JSON replaceOccurrencesOfString: @"\n"
				      withString: @"\\n"];

		[JSON prependString: @"\""];
		[JSON appendString: @"\""];
	}

	[JSON makeImmutable];

	return JSON;
}

- (OFDataArray*)messagePackRepresentation
{
	OFDataArray *data;
	size_t length;

	length = [self UTF8StringLength];

	if (length <= 31) {
		uint8_t tmp = 0xA0 | ((uint8_t)length & 0x1F);

		data = [OFDataArray dataArrayWithItemSize: 1
						 capacity: length + 1];

		[data addItem: &tmp];
	} else if (length <= UINT8_MAX) {
		uint8_t type = 0xD9;
		uint8_t tmp = (uint8_t)length;

		data = [OFDataArray dataArrayWithItemSize: 1
						 capacity: length + 2];

		[data addItem: &type];
		[data addItem: &tmp];
	} else if (length <= UINT16_MAX) {
		uint8_t type = 0xDA;
		uint16_t tmp = OF_BSWAP16_IF_LE((uint16_t)length);

		data = [OFDataArray dataArrayWithItemSize: 1
						 capacity: length + 3];

		[data addItem: &type];
		[data addItems: &tmp
			 count: sizeof(tmp)];
	} else if (length <= UINT32_MAX) {
		uint8_t type = 0xDB;
		uint32_t tmp = OF_BSWAP32_IF_LE((uint32_t)length);

		data = [OFDataArray dataArrayWithItemSize: 1
						 capacity: length + 5];

		[data addItem: &type];
		[data addItems: &tmp
			 count: sizeof(tmp)];
	} else
		@throw [OFOutOfRangeException exception];

	[data addItems: [self UTF8String]
		 count: length];

	return data;
}

- (of_range_t)rangeOfString: (OFString*)string
{
	return [self rangeOfString: string
			   options: 0
			     range: of_range(0, [self length])];
}

- (of_range_t)rangeOfString: (OFString*)string
		    options: (int)options
{
	return [self rangeOfString: string
			   options: options
			     range: of_range(0, [self length])];
}

- (of_range_t)rangeOfString: (OFString*)string
		    options: (int)options
		      range: (of_range_t)range
{
	void *pool;
	const of_unichar_t *searchCharacters;
	of_unichar_t *characters;
	size_t searchLength;

	if ((searchLength = [string length]) == 0)
		return of_range(0, 0);

	if (searchLength > range.length)
		return of_range(OF_NOT_FOUND, 0);

	if (range.length > SIZE_MAX / sizeof(of_unichar_t))
		@throw [OFOutOfRangeException exception];

	pool = objc_autoreleasePoolPush();

	searchCharacters = [string characters];
	characters = malloc(range.length * sizeof(of_unichar_t));

	if (characters == NULL)
		@throw [OFOutOfMemoryException exceptionWithRequestedSize:
		    range.length * sizeof(of_unichar_t)];

	@try {
		[self getCharacters: characters
			    inRange: range];

		if (options & OF_STRING_SEARCH_BACKWARDS) {
			for (size_t i = range.length - searchLength;; i--) {
				if (memcmp(characters + i, searchCharacters,
				    searchLength * sizeof(of_unichar_t)) == 0) {
					objc_autoreleasePoolPop(pool);
					return of_range(range.location + i,
					    searchLength);
				}

				/* No match and we're at the last character */
				if (i == 0)
					break;
			}
		} else {
			for (size_t i = 0;
			    i <= range.length - searchLength; i++) {
				if (memcmp(characters + i, searchCharacters,
				    searchLength * sizeof(of_unichar_t)) == 0) {
					objc_autoreleasePoolPop(pool);
					return of_range(range.location + i,
					    searchLength);
				}
			}
		}
	} @finally {
		free(characters);
	}

	objc_autoreleasePoolPop(pool);

	return of_range(OF_NOT_FOUND, 0);
}

- (bool)containsString: (OFString*)string
{
	void *pool;
	const of_unichar_t *characters, *searchCharacters;
	size_t length, searchLength;

	if ((searchLength = [string length]) == 0)
		return true;

	if (searchLength > (length = [self length]))
		return false;

	pool = objc_autoreleasePoolPush();

	characters = [self characters];
	searchCharacters = [string characters];

	for (size_t i = 0; i <= length - searchLength; i++) {
		if (memcmp(characters + i, searchCharacters,
		    searchLength * sizeof(of_unichar_t)) == 0) {
			objc_autoreleasePoolPop(pool);
			return true;
		}
	}

	objc_autoreleasePoolPop(pool);

	return false;
}

- (OFString*)substringWithRange: (of_range_t)range
{
	void *pool;
	OFString *ret;

	if (range.length > SIZE_MAX - range.location ||
	    range.location + range.length > [self length])
		@throw [OFOutOfRangeException exception];

	pool = objc_autoreleasePoolPush();
	ret = [[OFString alloc]
	    initWithCharacters: [self characters] + range.location
			length: range.length];
	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}

- (OFString*)stringByAppendingString: (OFString*)string
{
	OFMutableString *new;

	new = [OFMutableString stringWithString: self];
	[new appendString: string];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByAppendingFormat: (OFConstantString*)format, ...
{
	OFString *ret;
	va_list arguments;

	va_start(arguments, format);
	ret = [self stringByAppendingFormat: format
				  arguments: arguments];
	va_end(arguments);

	return ret;
}

- (OFString*)stringByAppendingFormat: (OFConstantString*)format
			   arguments: (va_list)arguments
{
	OFMutableString *new;

	new = [OFMutableString stringWithString: self];
	[new appendFormat: format
		arguments: arguments];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByAppendingPathComponent: (OFString*)component
{
	void *pool = objc_autoreleasePoolPush();
	OFString *ret;

	ret = [OFString pathWithComponents:
	    [OFArray arrayWithObjects: self, component, nil]];

	[ret retain];
	objc_autoreleasePoolPop(pool);
	return [ret autorelease];
}

- (OFString*)stringByPrependingString: (OFString*)string
{
	OFMutableString *new = [[string mutableCopy] autorelease];

	[new appendString: self];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByReplacingOccurrencesOfString: (OFString*)string
				       withString: (OFString*)replacement
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new replaceOccurrencesOfString: string
			     withString: replacement];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByReplacingOccurrencesOfString: (OFString*)string
				       withString: (OFString*)replacement
					  options: (int)options
					    range: (of_range_t)range
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new replaceOccurrencesOfString: string
			     withString: replacement
				options: options
				  range: range];

	[new makeImmutable];

	return new;
}

- (OFString*)uppercaseString
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new uppercase];

	[new makeImmutable];

	return new;
}

- (OFString*)lowercaseString
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new lowercase];

	[new makeImmutable];

	return new;
}

- (OFString*)capitalizedString
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new capitalize];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByDeletingLeadingWhitespaces
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new deleteLeadingWhitespaces];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByDeletingTrailingWhitespaces
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new deleteTrailingWhitespaces];

	[new makeImmutable];

	return new;
}

- (OFString*)stringByDeletingEnclosingWhitespaces
{
	OFMutableString *new = [[self mutableCopy] autorelease];

	[new deleteEnclosingWhitespaces];

	[new makeImmutable];

	return new;
}

- (bool)hasPrefix: (OFString*)prefix
{
	of_unichar_t *tmp;
	const of_unichar_t *prefixCharacters;
	size_t prefixLength;
	bool hasPrefix;

	if ((prefixLength = [prefix length]) > [self length])
		return false;

	tmp = [self allocMemoryWithSize: sizeof(of_unichar_t)
				  count: prefixLength];
	@try {
		void *pool = objc_autoreleasePoolPush();

		[self getCharacters: tmp
			    inRange: of_range(0, prefixLength)];

		prefixCharacters = [prefix characters];
		hasPrefix = (memcmp(tmp, prefixCharacters,
		    prefixLength * sizeof(of_unichar_t)) == 0);

		objc_autoreleasePoolPop(pool);
	} @finally {
		[self freeMemory: tmp];
	}

	return hasPrefix;
}

- (bool)hasSuffix: (OFString*)suffix
{
	of_unichar_t *tmp;
	const of_unichar_t *suffixCharacters;
	size_t length, suffixLength;
	bool hasSuffix;

	if ((suffixLength = [suffix length]) > [self length])
		return false;

	length = [self length];

	tmp = [self allocMemoryWithSize: sizeof(of_unichar_t)
				  count: suffixLength];
	@try {
		void *pool = objc_autoreleasePoolPush();

		[self getCharacters: tmp
			    inRange: of_range(length - suffixLength,
					 suffixLength)];

		suffixCharacters = [suffix characters];
		hasSuffix = (memcmp(tmp, suffixCharacters,
		    suffixLength * sizeof(of_unichar_t)) == 0);

		objc_autoreleasePoolPop(pool);
	} @finally {
		[self freeMemory: tmp];
	}

	return hasSuffix;
}

- (OFArray*)componentsSeparatedByString: (OFString*)delimiter
{
	return [self componentsSeparatedByString: delimiter
					 options: 0];
}

- (OFArray*)componentsSeparatedByString: (OFString*)delimiter
				options: (int)options
{
	void *pool;
	OFMutableArray *array = [OFMutableArray array];
	const of_unichar_t *characters, *delimiterCharacters;
	bool skipEmpty = (options & OF_STRING_SKIP_EMPTY);
	size_t length = [self length];
	size_t delimiterLength = [delimiter length];
	size_t last;
	OFString *component;

	pool = objc_autoreleasePoolPush();

	characters = [self characters];
	delimiterCharacters = [delimiter characters];

	if (delimiterLength > length) {
		[array addObject: [[self copy] autorelease]];
		[array makeImmutable];

		objc_autoreleasePoolPop(pool);

		return array;
	}

	last = 0;
	for (size_t i = 0; i <= length - delimiterLength; i++) {
		if (memcmp(characters + i, delimiterCharacters,
		    delimiterLength * sizeof(of_unichar_t)) != 0)
			continue;

		component = [self substringWithRange: of_range(last, i - last)];
		if (!skipEmpty || [component length] > 0)
			[array addObject: component];

		i += delimiterLength - 1;
		last = i + 1;
	}
	component = [self substringWithRange: of_range(last, length - last)];
	if (!skipEmpty || [component length] > 0)
		[array addObject: component];

	[array makeImmutable];

	objc_autoreleasePoolPop(pool);

	return array;
}

- (OFArray*)pathComponents
{
	OFMutableArray *ret;
	void *pool;
	const of_unichar_t *characters;
	size_t i, last = 0, length = [self length];

	ret = [OFMutableArray array];

	if (length == 0)
		return ret;

	pool = objc_autoreleasePoolPush();

	characters = [self characters];

	if (OF_IS_PATH_DELIMITER(characters[length - 1]))
		length--;

	for (i = 0; i < length; i++) {
		if (OF_IS_PATH_DELIMITER(characters[i])) {
			[ret addObject: [self substringWithRange:
			    of_range(last, i - last)]];

			last = i + 1;
		}
	}

	[ret addObject: [self substringWithRange: of_range(last, i - last)]];

#ifdef OF_WINDOWS
	if ([ret count] >= 2 && [[ret objectAtIndex: 0] hasSuffix: @":"]) {
		OFString *first = [[ret objectAtIndex: 0]
		    stringByAppendingPathComponent: [ret objectAtIndex: 1]];

		[ret removeObjectAtIndex: 0];
		[ret replaceObjectAtIndex: 0
			       withObject: first];
	}
#endif

	[ret makeImmutable];

	objc_autoreleasePoolPop(pool);

	return ret;
}

- (OFString*)lastPathComponent
{
	void *pool;
	const of_unichar_t *characters;
	size_t length = [self length];
	ssize_t i;

	if (length == 0)
		return @"";

	pool = objc_autoreleasePoolPush();

	characters = [self characters];

	if (OF_IS_PATH_DELIMITER(characters[length - 1]))
		length--;

	if (length == 0) {
		objc_autoreleasePoolPop(pool);

		return @"";
	}

	if (length - 1 > SSIZE_MAX)
		@throw [OFOutOfRangeException exception];

	for (i = length - 1; i >= 0; i--) {
		if (OF_IS_PATH_DELIMITER(characters[i])) {
			i++;
			break;
		}
	}

	objc_autoreleasePoolPop(pool);

	/*
	 * Only one component, but the trailing delimiter might have been
	 * removed, so return a new string anyway.
	 */
	if (i < 0)
		i = 0;

	return [self substringWithRange: of_range(i, length - i)];
}

- (OFString*)pathExtension
{
	void *pool = objc_autoreleasePoolPush();
	OFString *ret, *fileName;
	size_t pos;

	fileName = [self lastPathComponent];
	pos = [fileName rangeOfString: @"."
			      options: OF_STRING_SEARCH_BACKWARDS].location;
	if (pos == OF_NOT_FOUND || pos == 0)
		return @"";

	ret = [fileName substringWithRange:
	    of_range(pos + 1, [fileName length] - pos - 1)];

	[ret retain];
	objc_autoreleasePoolPop(pool);
	return [ret autorelease];
}

- (OFString*)stringByDeletingLastPathComponent
{
	void *pool;
	const of_unichar_t *characters;
	size_t length = [self length];

	if (length == 0)
		return @"";

	pool = objc_autoreleasePoolPush();

	characters = [self characters];

	if (OF_IS_PATH_DELIMITER(characters[length - 1]))
		length--;

	if (length == 0) {
		objc_autoreleasePoolPop(pool);
		return [self substringWithRange: of_range(0, 1)];
	}

	for (size_t i = length - 1; i >= 1; i--) {
		if (OF_IS_PATH_DELIMITER(characters[i])) {
			objc_autoreleasePoolPop(pool);
			return [self substringWithRange: of_range(0, i)];
		}
	}

	if (OF_IS_PATH_DELIMITER(characters[0])) {
		objc_autoreleasePoolPop(pool);
		return [self substringWithRange: of_range(0, 1)];
	}

	objc_autoreleasePoolPop(pool);

	return OF_PATH_CURRENT_DIRECTORY;
}

- (OFString*)stringByDeletingPathExtension
{
	void *pool;
	OFMutableArray *components;
	OFString *ret, *fileName;
	size_t pos;

	if ([self length] == 0)
		return [[self copy] autorelease];

	pool = objc_autoreleasePoolPush();
	components = [[[self pathComponents] mutableCopy] autorelease];
	fileName = [components lastObject];

	pos = [fileName rangeOfString: @"."
			      options: OF_STRING_SEARCH_BACKWARDS].location;
	if (pos == OF_NOT_FOUND || pos == 0) {
		objc_autoreleasePoolPop(pool);
		return [[self copy] autorelease];
	}

	fileName = [fileName substringWithRange: of_range(0, pos)];
	[components replaceObjectAtIndex: [components count] - 1
			      withObject: fileName];

	ret = [OFString pathWithComponents: components];

	[ret retain];
	objc_autoreleasePoolPop(pool);
	return [ret autorelease];
}

- (OFString*)stringByStandardizingPath
{
	return standardizePath([self pathComponents],
	    OF_PATH_CURRENT_DIRECTORY, OF_PATH_PARENT_DIRECTORY,
	    OF_PATH_DELIMITER_STRING);
}

- (OFString*)stringByStandardizingURLPath
{
	return standardizePath([self componentsSeparatedByString: @"/"],
	    @".", @"..", @"/");
}

- (intmax_t)decimalValue
{
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters = [self characters];
	size_t i = 0, length = [self length];
	intmax_t value = 0;
	bool expectWhitespace = false;

	while (length > 0 && (*characters == ' ' || *characters == '\t' ||
	    *characters == '\n' || *characters == '\r' ||
	    *characters == '\f')) {
		characters++;
		length--;
	}

	if (length == 0) {
		objc_autoreleasePoolPop(pool);
		return 0;
	}

	if (characters[0] == '-' || characters[0] == '+')
		i++;

	for (; i < length; i++) {
		if (expectWhitespace) {
			if (characters[i] != ' ' && characters[i] != '\t' &&
			    characters[i] != '\n' && characters[i] != '\r' &&
			    characters[i] != '\f')
				@throw [OFInvalidFormatException exception];
			continue;
		}

		if (characters[i] >= '0' && characters[i] <= '9') {
			if (INTMAX_MAX / 10 < value ||
			    INTMAX_MAX - value * 10 < characters[i] - '0')
				@throw [OFOutOfRangeException exception];

			value = (value * 10) + (characters[i] - '0');
		} else if (characters[i] == ' ' || characters[i] == '\t' ||
		    characters[i] == '\n' || characters[i] == '\r' ||
		    characters[i] == '\f')
			expectWhitespace = true;
		else
			@throw [OFInvalidFormatException exception];
	}

	if (characters[0] == '-')
		value *= -1;

	objc_autoreleasePoolPop(pool);

	return value;
}

- (uintmax_t)hexadecimalValue
{
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters = [self characters];
	size_t i = 0, length = [self length];
	uintmax_t value = 0;
	bool expectWhitespace = false, foundValue = false;

	while (length > 0 && (*characters == ' ' || *characters == '\t' ||
	    *characters == '\n' || *characters == '\r' ||
	    *characters == '\f')) {
		characters++;
		length--;
	}

	if (length == 0) {
		objc_autoreleasePoolPop(pool);
		return 0;
	}

	if (length >= 2 && characters[0] == '0' && characters[1] == 'x')
		i = 2;
	else if (length >= 1 && (characters[0] == 'x' || characters[0] == '$'))
		i = 1;

	for (; i < length; i++) {
		uintmax_t newValue;

		if (expectWhitespace) {
			if (characters[i] != ' ' && characters[i] != '\t' &&
			    characters[i] != '\n' && characters[i] != '\r' &&
			    characters[i] != '\f')
				@throw [OFInvalidFormatException exception];
			continue;
		}

		if (characters[i] >= '0' && characters[i] <= '9') {
			newValue = (value << 4) | (characters[i] - '0');
			foundValue = true;
		} else if (characters[i] >= 'A' && characters[i] <= 'F') {
			newValue = (value << 4) | (characters[i] - 'A' + 10);
			foundValue = true;
		} else if (characters[i] >= 'a' && characters[i] <= 'f') {
			newValue = (value << 4) | (characters[i] - 'a' + 10);
			foundValue = true;
		} else if (characters[i] == 'h' || characters[i] == ' ' ||
		    characters[i] == '\t' || characters[i] == '\n' ||
		    characters[i] == '\r' || characters[i] == '\f') {
			expectWhitespace = true;
			continue;
		} else
			@throw [OFInvalidFormatException exception];

		if (newValue < value)
			@throw [OFOutOfRangeException exception];

		value = newValue;
	}

	if (!foundValue)
		@throw [OFInvalidFormatException exception];

	objc_autoreleasePoolPop(pool);

	return value;
}

- (uintmax_t)octalValue
{
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters = [self characters];
	size_t i = 0, length = [self length];
	uintmax_t value = 0;
	bool expectWhitespace = false;

	while (length > 0 && (*characters == ' ' || *characters == '\t' ||
	    *characters == '\n' || *characters == '\r' ||
	    *characters == '\f')) {
		characters++;
		length--;
	}

	if (length == 0) {
		objc_autoreleasePoolPop(pool);
		return 0;
	}

	for (; i < length; i++) {
		uintmax_t newValue;

		if (expectWhitespace) {
			if (characters[i] != ' ' && characters[i] != '\t' &&
			    characters[i] != '\n' && characters[i] != '\r' &&
			    characters[i] != '\f')
				@throw [OFInvalidFormatException exception];
			continue;
		}

		if (characters[i] >= '0' && characters[i] <= '7')
			newValue = (value << 3) | (characters[i] - '0');
		else if (characters[i] == ' ' || characters[i] == '\t' ||
		    characters[i] == '\n' || characters[i] == '\r' ||
		    characters[i] == '\f') {
			expectWhitespace = true;
			continue;
		} else
			@throw [OFInvalidFormatException exception];

		if (newValue < value)
			@throw [OFOutOfRangeException exception];

		value = newValue;
	}

	objc_autoreleasePoolPop(pool);

	return value;
}

- (float)floatValue
{
	void *pool = objc_autoreleasePoolPush();
	OFString *decimalPoint = [OFSystemInfo decimalPoint];
	const char *UTF8String = [[self
	    stringByReplacingOccurrencesOfString: @"."
				      withString: decimalPoint] UTF8String];
	char *endPointer = NULL;
	float value;

	while (*UTF8String == ' ' || *UTF8String == '\t' ||
	    *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f')
		UTF8String++;

	value = strtof(UTF8String, &endPointer);

	/* Check if there are any invalid chars left */
	if (endPointer != NULL)
		for (; *endPointer != '\0'; endPointer++)
			if (*endPointer != ' ' && *endPointer != '\t' &&
			    *endPointer != '\n' && *endPointer != '\r' &&
			    *endPointer != '\f')
				@throw [OFInvalidFormatException exception];

	objc_autoreleasePoolPop(pool);

	return value;
}

- (double)doubleValue
{
	void *pool = objc_autoreleasePoolPush();
	OFString *decimalPoint = [OFSystemInfo decimalPoint];
	const char *UTF8String = [[self
	    stringByReplacingOccurrencesOfString: @"."
				      withString: decimalPoint] UTF8String];
	char *endPointer = NULL;
	double value;

	while (*UTF8String == ' ' || *UTF8String == '\t' ||
	    *UTF8String == '\n' || *UTF8String == '\r' || *UTF8String == '\f')
		UTF8String++;

	value = strtod(UTF8String, &endPointer);

	/* Check if there are any invalid chars left */
	if (endPointer != NULL)
		for (; *endPointer != '\0'; endPointer++)
			if (*endPointer != ' ' && *endPointer != '\t' &&
			    *endPointer != '\n' && *endPointer != '\r' &&
			    *endPointer != '\f')
				@throw [OFInvalidFormatException exception];

	objc_autoreleasePoolPop(pool);

	return value;
}

- (const of_unichar_t*)characters
{
	OFObject *object = [[[OFObject alloc] init] autorelease];
	size_t length = [self length];
	of_unichar_t *ret;

	ret = [object allocMemoryWithSize: sizeof(of_unichar_t)
				    count: length];
	[self getCharacters: ret
		    inRange: of_range(0, length)];

	return ret;
}

- (const of_char16_t*)UTF16String
{
	return [self UTF16StringWithByteOrder: OF_BYTE_ORDER_NATIVE];
}

- (const of_char16_t*)UTF16StringWithByteOrder: (of_byte_order_t)byteOrder
{
	OFObject *object = [[[OFObject alloc] init] autorelease];
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters = [self characters];
	size_t length = [self length];
	of_char16_t *ret;
	size_t j;
	bool swap = (byteOrder != OF_BYTE_ORDER_NATIVE);

	/* Allocate memory for the worst case */
	ret = [object allocMemoryWithSize: sizeof(of_char16_t)
				    count: (length + 1) * 2];

	j = 0;
	for (size_t i = 0; i < length; i++) {
		of_unichar_t c = characters[i];

		if (c > 0x10FFFF)
			@throw [OFInvalidEncodingException exception];

		if (swap) {
			if (c > 0xFFFF) {
				c -= 0x10000;
				ret[j++] = OF_BSWAP16(0xD800 | (c >> 10));
				ret[j++] = OF_BSWAP16(0xDC00 | (c & 0x3FF));
			} else
				ret[j++] = OF_BSWAP16(c);
		} else {
			if (c > 0xFFFF) {
				c -= 0x10000;
				ret[j++] = 0xD800 | (c >> 10);
				ret[j++] = 0xDC00 | (c & 0x3FF);
			} else
				ret[j++] = c;
		}
	}
	ret[j] = 0;

	@try {
		ret = [object resizeMemory: ret
				      size: sizeof(of_char16_t)
				     count: j + 1];
	} @catch (OFOutOfMemoryException *e) {
		/* We don't care, as we only tried to make it smaller */
	}

	objc_autoreleasePoolPop(pool);

	return ret;
}

- (size_t)UTF16StringLength
{
	const of_unichar_t *characters = [self characters];
	size_t length, UTF16StringLength;

	length = UTF16StringLength = [self length];

	for (size_t i = 0; i < length; i++)
		if (characters[i] > 0xFFFF)
			UTF16StringLength++;

	return UTF16StringLength;
}

- (const of_char32_t*)UTF32String
{
	return [self UTF32StringWithByteOrder: OF_BYTE_ORDER_NATIVE];
}

- (const of_char32_t*)UTF32StringWithByteOrder: (of_byte_order_t)byteOrder
{
	OFObject *object = [[[OFObject alloc] init] autorelease];
	size_t length = [self length];
	of_char32_t *ret;

	ret = [object allocMemoryWithSize: sizeof(of_char32_t)
				    count: length + 1];
	[self getCharacters: ret
		    inRange: of_range(0, length)];
	ret[length] = 0;

	if (byteOrder != OF_BYTE_ORDER_NATIVE)
		for (size_t i = 0; i < length; i++)
			ret[i] = OF_BSWAP32(ret[i]);

	return ret;
}

#ifdef OF_HAVE_FILES
- (void)writeToFile: (OFString*)path
{
	[self writeToFile: path
		 encoding: OF_STRING_ENCODING_UTF_8];
}

- (void)writeToFile: (OFString*)path
	   encoding: (of_string_encoding_t)encoding
{
	void *pool = objc_autoreleasePoolPush();
	OFFile *file;

	file = [OFFile fileWithPath: path
			       mode: @"wb"];
	[file writeString: self
		 encoding: encoding];

	objc_autoreleasePoolPop(pool);
}
#endif

#ifdef OF_HAVE_BLOCKS
- (void)enumerateLinesUsingBlock: (of_string_line_enumeration_block_t)block
{
	void *pool = objc_autoreleasePoolPush();
	const of_unichar_t *characters = [self characters];
	size_t i, last = 0, length = [self length];
	bool stop = false, lastCarriageReturn = false;

	for (i = 0; i < length && !stop; i++) {
		if (lastCarriageReturn && characters[i] == '\n') {
			lastCarriageReturn = false;
			last++;

			continue;
		}

		if (characters[i] == '\n' || characters[i] == '\r') {
			void *pool2 = objc_autoreleasePoolPush();

			block([self substringWithRange:
			    of_range(last, i - last)], &stop);
			last = i + 1;

			objc_autoreleasePoolPop(pool2);
		}

		lastCarriageReturn = (characters[i] == '\r');
	}

	if (!stop)
		block([self substringWithRange: of_range(last, i - last)],
		    &stop);

	objc_autoreleasePoolPop(pool);
}
#endif
@end