ObjFW  Check-in [3b2697b2a7]

Overview
Comment:Make relative URIs behave as per RFC 3986
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 3b2697b2a74be10397a6e065f473c22d06d524dde8e77ccc34da91eadc2e89a0
User & Date: js on 2022-10-10 23:50:29
Other Links: manifest | tags
References
2022-10-11
00:23 Fixed ticket [44ec7f4c75]: Add support for URIs plus 4 other changes artifact: ebc6c953a2 user: js
Context
2022-10-11
00:18
OFURI: Remove percentEncodedScheme check-in: 13ead1212f user: js tags: trunk
2022-10-10
23:50
Make relative URIs behave as per RFC 3986 check-in: 3b2697b2a7 user: js tags: trunk
19:22
OFURI: Enforce scheme starts with a letter check-in: 12d95e15ae user: js tags: trunk
Changes

Modified src/OFURI.m from [d2aca0a264] to [2fc8b40e3d].

544
545
546
547
548
549
550
551

552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593

594
595
596
597
598
599
600

	parseHostPort(self, UTF8String, length);

	return ret;
}

static void
parsePathQueryFragment(OFURI *self, const char *UTF8String, size_t length)

{
	const char *fragment, *query;

	if ((fragment = memchr(UTF8String, '#', length)) != NULL) {
		self->_percentEncodedFragment = [[OFString alloc]
		    initWithUTF8String: fragment + 1
				length: length - (fragment - UTF8String) - 1];

		OFURIVerifyIsEscaped(self->_percentEncodedFragment,
		    [OFCharacterSet URIQueryAllowedCharacterSet]);

		length = fragment - UTF8String;
	}

	if ((query = memchr(UTF8String, '?', length)) != NULL) {
		self->_percentEncodedQuery = [[OFString alloc]
		    initWithUTF8String: query + 1
				length: length - (query - UTF8String) - 1];

		OFURIVerifyIsEscaped(self->_percentEncodedQuery,
		    [OFCharacterSet URIFragmentAllowedCharacterSet]);

		length = query - UTF8String;
	}

	self->_percentEncodedPath = [[OFString alloc]
	    initWithUTF8String: UTF8String
			length: length];

	OFURIVerifyIsEscaped(self->_percentEncodedPath,
	    [OFCharacterSet URIQueryAllowedCharacterSet]);
}

- (instancetype)initWithString: (OFString *)string
{
	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		const char *UTF8String = string.UTF8String;
		size_t length = string.UTF8StringLength;
		const char *colon;


		if ((colon = strchr(UTF8String, ':')) == NULL ||
		    colon - UTF8String < 1 || !OFASCIIIsAlpha(UTF8String[0]))
			@throw [OFInvalidFormatException exception];

		_percentEncodedScheme = [[[OFString
		    stringWithUTF8String: UTF8String







|
>




|
|
|

|






|
|
|

|





<
|
|

|












>







544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577

578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601

	parseHostPort(self, UTF8String, length);

	return ret;
}

static void
parsePathQueryFragment(const char *UTF8String, size_t length,
    OFString **pathString, OFString **queryString, OFString **fragmentString)
{
	const char *fragment, *query;

	if ((fragment = memchr(UTF8String, '#', length)) != NULL) {
		*fragmentString = [OFString
		    stringWithUTF8String: fragment + 1
				  length: length - (fragment - UTF8String) - 1];

		OFURIVerifyIsEscaped(*fragmentString,
		    [OFCharacterSet URIQueryAllowedCharacterSet]);

		length = fragment - UTF8String;
	}

	if ((query = memchr(UTF8String, '?', length)) != NULL) {
		*queryString = [OFString
		    stringWithUTF8String: query + 1
				  length: length - (query - UTF8String) - 1];

		OFURIVerifyIsEscaped(*queryString,
		    [OFCharacterSet URIFragmentAllowedCharacterSet]);

		length = query - UTF8String;
	}


	*pathString = [OFString stringWithUTF8String: UTF8String
					      length: length];

	OFURIVerifyIsEscaped(*pathString,
	    [OFCharacterSet URIQueryAllowedCharacterSet]);
}

- (instancetype)initWithString: (OFString *)string
{
	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		const char *UTF8String = string.UTF8String;
		size_t length = string.UTF8StringLength;
		const char *colon;
		OFString *path, *query = nil, *fragment = nil;

		if ((colon = strchr(UTF8String, ':')) == NULL ||
		    colon - UTF8String < 1 || !OFASCIIIsAlpha(UTF8String[0]))
			@throw [OFInvalidFormatException exception];

		_percentEncodedScheme = [[[OFString
		    stringWithUTF8String: UTF8String
620
621
622
623
624
625
626
627




628
629
630
631
632
633
634
635
636
637



















































638
639

640





641
642

643
644
645
646
647
648
649



650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666

667
668
669
670
671
672
673
674


675


676
677
678
679
680
681






682



683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709


710

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
			UTF8String += authorityLength;
			length -= authorityLength;

			if (length > 0)
				OFEnsure(UTF8String[0] == '/');
		}

		parsePathQueryFragment(self, UTF8String, length);





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

	return self;
}




















































- (instancetype)initWithString: (OFString *)string relativeToURI: (OFURI *)URI
{

	char *UTF8String, *UTF8String2 = NULL;






	if ([string containsString: @"://"])

		return [self initWithString: string];

	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		char *tmp;




		_percentEncodedScheme = [URI->_percentEncodedScheme copy];
		_percentEncodedHost = [URI->_percentEncodedHost copy];
		_port = [URI->_port copy];
		_percentEncodedUser = [URI->_percentEncodedUser copy];
		_percentEncodedPassword = [URI->_percentEncodedPassword copy];

		UTF8String = UTF8String2 = OFStrDup(string.UTF8String);

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

			OFURIVerifyIsEscaped(_percentEncodedFragment,
			    [OFCharacterSet URIFragmentAllowedCharacterSet]);
		}


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

			OFURIVerifyIsEscaped(_percentEncodedQuery,
			    [OFCharacterSet URIQueryAllowedCharacterSet]);


		}



		if (*UTF8String == '/')
			_percentEncodedPath = [[OFString alloc]
			    initWithUTF8String: UTF8String];
		else {
			OFString *relativePath =






			    [OFString stringWithUTF8String: UTF8String];




			if ([URI->_percentEncodedPath hasSuffix: @"/"])
				_percentEncodedPath = [[URI->_percentEncodedPath
				    stringByAppendingString: relativePath]
				    copy];
			else {
				OFMutableString *path = [OFMutableString
				    stringWithString:
				    (URI->_percentEncodedPath.length > 0
				    ? URI->_percentEncodedPath
				    : @"/")];
				OFRange range = [path
				    rangeOfString: @"/"
					  options: OFStringSearchBackwards];

				if (range.location == OFNotFound)
					@throw [OFInvalidFormatException
					    exception];

				range.location++;
				range.length = path.length - range.location;

				[path replaceCharactersInRange: range
						    withString: relativePath];
				[path makeImmutable];

				_percentEncodedPath = [path copy];


			}

		}

		OFURIVerifyIsEscaped(_percentEncodedPath,
		    [OFCharacterSet URIPathAllowedCharacterSet]);

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

	return self;
}

#ifdef OF_HAVE_FILES
- (instancetype)initFileURIWithPath: (OFString *)path







|
>
>
>
>










>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>


>
|
>
>
>
>
>
|
|
>






|
>
>
>


<
<
<
<

<
|
<
<
<
|
|
<
<
|
>

|
|
<
<

<
<
>
>
|
>
>

|
<
|
|
<
>
>
>
>
>
>
|
>
>
>

|
|
<
|
|
|
<
|
|
<
|
<
<
|
<
<
<
|
<
<
|
|
<
|
|
|
>
>
|
>
|
|
<
<





<
<







621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717




718

719



720
721


722
723
724
725
726


727


728
729
730
731
732
733
734

735
736

737
738
739
740
741
742
743
744
745
746
747
748
749

750
751
752

753
754

755


756



757


758
759

760
761
762
763
764
765
766
767
768


769
770
771
772
773


774
775
776
777
778
779
780
			UTF8String += authorityLength;
			length -= authorityLength;

			if (length > 0)
				OFEnsure(UTF8String[0] == '/');
		}

		parsePathQueryFragment(UTF8String, length,
		    &path, &query, &fragment);
		_percentEncodedPath = [path copy];
		_percentEncodedQuery = [query copy];
		_percentEncodedFragment = [fragment copy];

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

	return self;
}

static bool
isAbsolute(OFString *string)
{
	void *pool = objc_autoreleasePoolPush();

	@try {
		const char *UTF8String = string.UTF8String;
		size_t length = string.UTF8StringLength;

		if (length < 1)
			return false;

		if (!OFASCIIIsAlpha(UTF8String[0]))
			return false;

		for (size_t i = 1; i < length; i++) {
			if (UTF8String[i] == ':')
				return true;

			if (!OFASCIIIsAlnum(UTF8String[i]) &&
			    UTF8String[i] != '+' && UTF8String[i] != '-' &&
			    UTF8String[i] != '.')
				return false;
		}
	} @finally {
		objc_autoreleasePoolPop(pool);
	}

	return false;
}

static OFString *
merge(OFString *base, OFString *path)
{
	OFMutableArray *components;

	if (base.length == 0)
		base = @"/";

	components = [[[base componentsSeparatedByString: @"/"]
	    mutableCopy] autorelease];

	if (components.count == 1)
		[components addObject: path];
	else
		[components replaceObjectAtIndex: components.count - 1
				      withObject: path];

	return [components componentsJoinedByString: @"/"];
}

- (instancetype)initWithString: (OFString *)string relativeToURI: (OFURI *)URI
{
	bool absolute;

	@try {
		absolute = isAbsolute(string);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	if (absolute)
		return [self initWithString: string];

	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		const char *UTF8String = string.UTF8String;
		size_t length = string.UTF8StringLength;
		bool hasAuthority = false;
		OFString *path, *query = nil, *fragment = nil;

		_percentEncodedScheme = [URI->_percentEncodedScheme copy];






		if (length >= 2 && UTF8String[0] == '/' &&



		    UTF8String[1] == '/') {
			size_t authorityLength;



			hasAuthority = true;

			UTF8String += 2;
			length -= 2;





			authorityLength = parseAuthority(self,
			    UTF8String, length);

			UTF8String += authorityLength;
			length -= authorityLength;

			if (length > 0)

				OFEnsure(UTF8String[0] == '/');
		} else {

			_percentEncodedHost = [URI->_percentEncodedHost copy];
			_port = [URI->_port copy];
			_percentEncodedUser = [URI->_percentEncodedUser copy];
			_percentEncodedPassword =
			    [URI->_percentEncodedPassword copy];
		}

		parsePathQueryFragment(UTF8String, length,
		    &path, &query, &fragment);
		_percentEncodedFragment = [fragment copy];

		if (hasAuthority) {
			_percentEncodedPath = [path copy];

			_percentEncodedQuery = [query copy];
		} else {
			if (path.length == 0) {

				_percentEncodedPath =
				    [URI->_percentEncodedPath copy];

				_percentEncodedQuery = (query != nil


				    ? [query copy]



				    : [URI->_percentEncodedQuery copy]);


			} else {
				if ([path hasPrefix: @"/"])

					_percentEncodedPath = [path copy];
				else
					_percentEncodedPath = [merge(
					    URI->_percentEncodedPath, path)
					    copy];

				_percentEncodedQuery = [query copy];
			}
		}



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


	}

	return self;
}

#ifdef OF_HAVE_FILES
- (instancetype)initFileURIWithPath: (OFString *)path