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
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(OFURI *self, const char *UTF8String, size_t length)
parsePathQueryFragment(const char *UTF8String, size_t length,
    OFString **pathString, OFString **queryString, OFString **fragmentString)
{
	const char *fragment, *query;

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

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

		length = fragment - UTF8String;
	}

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

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

		length = query - UTF8String;
	}

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

	OFURIVerifyIsEscaped(self->_percentEncodedPath,
	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
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(self, UTF8String, length);
		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;
	char *UTF8String, *UTF8String2 = NULL;

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

	@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();
		char *tmp;
		const char *UTF8String = string.UTF8String;
		size_t length = string.UTF8StringLength;
		bool hasAuthority = false;
		OFString *path, *query = nil, *fragment = nil;

		_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 (length >= 2 && UTF8String[0] == '/' &&
		if ((tmp = strchr(UTF8String, '#')) != NULL) {
			*tmp = '\0';
			_percentEncodedFragment = [[OFString alloc]
			    initWithUTF8String: tmp + 1];

		    UTF8String[1] == '/') {
			size_t authorityLength;
			OFURIVerifyIsEscaped(_percentEncodedFragment,
			    [OFCharacterSet URIFragmentAllowedCharacterSet]);
		}

			hasAuthority = true;

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

			OFURIVerifyIsEscaped(_percentEncodedQuery,
			    [OFCharacterSet URIQueryAllowedCharacterSet]);
		}
			authorityLength = parseAuthority(self,
			    UTF8String, length);

			UTF8String += authorityLength;
			length -= authorityLength;

		if (*UTF8String == '/')
			if (length > 0)
			_percentEncodedPath = [[OFString alloc]
			    initWithUTF8String: UTF8String];
		else {
				OFEnsure(UTF8String[0] == '/');
		} else {
			OFString *relativePath =
			    [OFString stringWithUTF8String: UTF8String];
			_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 ([URI->_percentEncodedPath hasSuffix: @"/"])
				_percentEncodedPath = [[URI->_percentEncodedPath
		if (hasAuthority) {
			_percentEncodedPath = [path copy];
				    stringByAppendingString: relativePath]
				    copy];
			else {
				OFMutableString *path = [OFMutableString
			_percentEncodedQuery = [query copy];
		} else {
			if (path.length == 0) {
				    stringWithString:
				    (URI->_percentEncodedPath.length > 0
				    ? URI->_percentEncodedPath
				_percentEncodedPath =
				    [URI->_percentEncodedPath copy];
				    : @"/")];
				OFRange range = [path
				_percentEncodedQuery = (query != nil
				    rangeOfString: @"/"
					  options: OFStringSearchBackwards];

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

				    : [URI->_percentEncodedQuery copy]);
				range.location++;
				range.length = path.length - range.location;

				[path replaceCharactersInRange: range
			} else {
				if ([path hasPrefix: @"/"])
						    withString: relativePath];
				[path makeImmutable];

				_percentEncodedPath = [path copy];
			}
		}

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

				_percentEncodedQuery = [query 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