ObjFW  Artifact [bcc6187243]

Artifact bcc6187243c076ffcc8813fab90d993c152affe99043eab8d453042422adbc0d:

  • File src/OFURL.m — part of check-in [13ee56edf3] at 2014-06-21 21:43:43 on branch trunk — Move all macros from OFObject.h to macros.h

    This means that OFObject.h imports macros.h now, making it unnecessary
    to manually import macros.h in almost every file. And while at it, also
    import autorelease.h in OFObject.h, so that this doesn't need to be
    manually imported in almost every file as well. (user: js, size: 10458) [annotate] [blame] [check-ins using]


/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014
 *   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.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 <stdlib.h>
#include <string.h>

#import "OFURL.h"
#import "OFString.h"
#import "OFArray.h"
#import "OFXMLElement.h"

#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfMemoryException.h"

@implementation OFURL
+ (instancetype)URL
{
	return [[[self alloc] init] autorelease];
}

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

+ (instancetype)URLWithString: (OFString*)string
		relativeToURL: (OFURL*)URL
{
	return [[[self alloc] initWithString: string
			       relativeToURL: URL] autorelease];
}

- initWithString: (OFString*)string
{
	char *UTF8String, *UTF8String2 = NULL;

	self = [super init];

	@try {
		char *tmp, *tmp2;

		if ((UTF8String2 = of_strdup([string UTF8String])) == NULL)
			@throw [OFOutOfMemoryException
			     exceptionWithRequestedSize: [string
							     UTF8StringLength]];

		UTF8String = UTF8String2;

		if (!strncmp(UTF8String, "file://", 7)) {
			_scheme = @"file";
			_path = [[OFString alloc]
			    initWithUTF8String: UTF8String + 7];
			return self;
		} else if (!strncmp(UTF8String, "http://", 7)) {
			_scheme = @"http";
			UTF8String += 7;
		} else if (!strncmp(UTF8String, "https://", 8)) {
			_scheme = @"https";
			UTF8String += 8;
		} else
			@throw [OFInvalidFormatException exception];

		if ((tmp = strchr(UTF8String, '/')) != NULL) {
			*tmp = '\0';
			tmp++;
		}

		if ((tmp2 = strchr(UTF8String, '@')) != NULL) {
			char *tmp3;

			*tmp2 = '\0';
			tmp2++;

			if ((tmp3 = strchr(UTF8String, ':')) != NULL) {
				*tmp3 = '\0';
				tmp3++;

				_user = [[OFString alloc]
				    initWithUTF8String: UTF8String];
				_password = [[OFString alloc]
				    initWithUTF8String: tmp3];
			} else
				_user = [[OFString alloc]
				    initWithUTF8String: UTF8String];

			UTF8String = tmp2;
		}

		if ((tmp2 = strchr(UTF8String, ':')) != NULL) {
			void *pool;
			OFString *portString;

			*tmp2 = '\0';
			tmp2++;

			_host = [[OFString alloc]
			    initWithUTF8String: UTF8String];

			pool = objc_autoreleasePoolPush();
			portString = [OFString stringWithUTF8String: tmp2];

			if ([portString decimalValue] > 65535)
				@throw [OFInvalidFormatException exception];

			_port = [portString decimalValue];

			if (_port == 0)
				_port = 80;

			objc_autoreleasePoolPop(pool);
		} else {
			_host = [[OFString alloc]
			    initWithUTF8String: UTF8String];

			if ([_scheme isEqual: @"http"])
				_port = 80;
			else if ([_scheme isEqual: @"https"])
				_port = 443;
			else
				OF_ENSURE(0);
		}

		if ((UTF8String = tmp) != NULL) {
			if ((tmp = strchr(UTF8String, '#')) != NULL) {
				*tmp = '\0';

				_fragment = [[OFString alloc]
				    initWithUTF8String: tmp + 1];
			}

			if ((tmp = strchr(UTF8String, '?')) != NULL) {
				*tmp = '\0';

				_query = [[OFString alloc]
				    initWithUTF8String: tmp + 1];
			}

			if ((tmp = strchr(UTF8String, ';')) != NULL) {
				*tmp = '\0';

				_parameters = [[OFString alloc]
				    initWithUTF8String: tmp + 1];
			}

			_path = [[OFString alloc] initWithFormat: @"/%s",
								  UTF8String];
		} else
			_path = @"";
	} @catch (id e) {
		[self release];
		@throw e;
	} @finally {
		free(UTF8String2);
	}

	return self;
}

- initWithString: (OFString*)string
   relativeToURL: (OFURL*)URL
{
	char *UTF8String, *UTF8String2 = NULL;

	if ([string containsString: @"://"])
		return [self initWithString: string];

	self = [super init];

	@try {
		char *tmp;

		_scheme = [URL->_scheme copy];
		_host = [URL->_host copy];
		_port = URL->_port;
		_user = [URL->_user copy];
		_password = [URL->_password copy];

		if ((UTF8String2 = of_strdup([string UTF8String])) == NULL)
			@throw [OFOutOfMemoryException
			     exceptionWithRequestedSize:
			     [string UTF8StringLength]];

		UTF8String = UTF8String2;

		if ((tmp = strchr(UTF8String, '#')) != NULL) {
			*tmp = '\0';
			_fragment = [[OFString alloc]
			    initWithUTF8String: tmp + 1];
		}

		if ((tmp = strchr(UTF8String, '?')) != NULL) {
			*tmp = '\0';
			_query = [[OFString alloc] initWithUTF8String: tmp + 1];
		}

		if ((tmp = strchr(UTF8String, ';')) != NULL) {
			*tmp = '\0';
			_parameters = [[OFString alloc]
			    initWithUTF8String: tmp + 1];
		}

		if (*UTF8String == '/')
			_path = [[OFString alloc]
			    initWithUTF8String: UTF8String];
		else {
			void *pool;
			OFString *s;

			pool = objc_autoreleasePoolPush();

			if ([URL->_path hasSuffix: @"/"])
				s = [OFString stringWithFormat: @"%@%s",
								URL->_path,
								UTF8String];
			else
				s = [OFString stringWithFormat: @"%@/../%s",
								URL->_path,
								UTF8String];

			_path = [[s stringByStandardizingURLPath] copy];

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

	return self;
}

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

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

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

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

	return self;
}

- (void)dealloc
{
	[_scheme release];
	[_host release];
	[_user release];
	[_password release];
	[_path release];
	[_parameters release];
	[_query release];
	[_fragment release];

	[super dealloc];
}

- (bool)isEqual: (id)object
{
	OFURL *URL;

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

	URL = object;

	if (![URL->_scheme isEqual: _scheme])
		return false;
	if (![URL->_host isEqual: _host])
		return false;
	if (URL->_port != _port)
		return false;
	if (URL->_user != _user && ![URL->_user isEqual: _user])
		return false;
	if (URL->_password != _password &&
	    ![URL->_password isEqual: _password])
		return false;
	if (![URL->_path isEqual: _path])
		return false;
	if (URL->_parameters != _parameters &&
	    ![URL->_parameters isEqual: _parameters])
		return false;
	if (URL->_query != _query &&
	    ![URL->_query isEqual: _query])
		return false;
	if (URL->_fragment != _fragment &&
	    ![URL->_fragment isEqual: _fragment])
		return false;

	return true;
}

- (uint32_t)hash
{
	uint32_t hash;

	OF_HASH_INIT(hash);

	OF_HASH_ADD_HASH(hash, [_scheme hash]);
	OF_HASH_ADD_HASH(hash, [_host hash]);
	OF_HASH_ADD(hash, (_port & 0xFF00) >> 8);
	OF_HASH_ADD(hash, _port & 0xFF);
	OF_HASH_ADD_HASH(hash, [_user hash]);
	OF_HASH_ADD_HASH(hash, [_password hash]);
	OF_HASH_ADD_HASH(hash, [_path hash]);
	OF_HASH_ADD_HASH(hash, [_parameters hash]);
	OF_HASH_ADD_HASH(hash, [_query hash]);
	OF_HASH_ADD_HASH(hash, [_fragment hash]);

	OF_HASH_FINALIZE(hash);

	return hash;
}

- copy
{
	OFURL *copy = [[[self class] alloc] init];

	@try {
		copy->_scheme = [_scheme copy];
		copy->_host = [_host copy];
		copy->_port = _port;
		copy->_user = [_user copy];
		copy->_password = [_password copy];
		copy->_path = [_path copy];
		copy->_parameters = [_parameters copy];
		copy->_query = [_query copy];
		copy->_fragment = [_fragment copy];
	} @catch (id e) {
		[copy release];
		@throw e;
	}

	return copy;
}

- (OFString*)scheme
{
	OF_GETTER(_scheme, true)
}

- (void)setScheme: (OFString*)scheme
{
	if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"])
		@throw [OFInvalidArgumentException exception];

	OF_SETTER(_scheme, scheme, true, 1)
}

- (OFString*)host
{
	OF_GETTER(_host, true)
}

- (void)setHost: (OFString*)host
{
	OF_SETTER(_host, host, true, 1)
}

- (uint16_t)port
{
	return _port;
}

- (void)setPort: (uint16_t)port
{
	_port = port;
}

- (OFString*)user
{
	OF_GETTER(_user, true)
}

- (void)setUser: (OFString*)user
{
	OF_SETTER(_user, user, true, 1)
}

- (OFString*)password
{
	OF_GETTER(_password, true)
}

- (void)setPassword: (OFString*)password
{
	OF_SETTER(_password, password, true, 1)
}

- (OFString*)path
{
	OF_GETTER(_path, true)
}

- (void)setPath: (OFString*)path
{
	if (([_scheme isEqual: @"http"] || [_scheme isEqual: @"https"]) &&
	    ![path hasPrefix: @"/"])
		@throw [OFInvalidArgumentException exception];

	OF_SETTER(_path, path, true, 1)
}

- (OFString*)parameters
{
	OF_GETTER(_parameters, true)
}

- (void)setParameters: (OFString*)parameters
{
	OF_SETTER(_parameters, parameters, true, 1)
}

- (OFString*)query
{
	OF_GETTER(_query, true)
}

- (void)setQuery: (OFString*)query
{
	OF_SETTER(_query, query, true, 1)
}

- (OFString*)fragment
{
	OF_GETTER(_fragment, true)
}

- (void)setFragment: (OFString*)fragment
{
	OF_SETTER(_fragment, fragment, true, 1)
}

- (OFString*)string
{
	OFMutableString *ret = [OFMutableString stringWithFormat: @"%@://",
								  _scheme];

	if ([_scheme isEqual: @"file"]) {
		if (_path != nil)
			[ret appendString: _path];

		return ret;
	}

	if (_user != nil && _password != nil)
		[ret appendFormat: @"%@:%@@", _user, _password];
	else if (_user != nil)
		[ret appendFormat: @"%@@", _user];

	if (_host != nil)
		[ret appendString: _host];

	if (([_scheme isEqual: @"http"] && _port != 80) ||
	    ([_scheme isEqual: @"https"] && _port != 443))
		[ret appendFormat: @":%u", _port];

	if (_path != nil)
		[ret appendString: _path];

	if (_parameters != nil)
		[ret appendFormat: @";%@", _parameters];

	if (_query != nil)
		[ret appendFormat: @"?%@", _query];

	if (_fragment != nil)
		[ret appendFormat: @"#%@", _fragment];

	[ret makeImmutable];

	return ret;
}

- (OFString*)description
{
	return [OFString stringWithFormat: @"<%@: %@>",
					   [self class], [self string]];
}

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

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

	[element retain];

	objc_autoreleasePoolPop(pool);

	return [element autorelease];
}
@end