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
		*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
		*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
	*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];

		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];

			_percentEncodedPassword =
			    [URI->_percentEncodedPassword copy];
		}
		UTF8String = UTF8String2 = OFStrDup(string.UTF8String);

		if ((tmp = strchr(UTF8String, '#')) != NULL) {
			*tmp = '\0';
			_percentEncodedFragment = [[OFString alloc]
		parsePathQueryFragment(UTF8String, length,
		    &path, &query, &fragment);
		_percentEncodedFragment = [fragment copy];
			    initWithUTF8String: tmp + 1];

		if (hasAuthority) {
			OFURIVerifyIsEscaped(_percentEncodedFragment,
			    [OFCharacterSet URIFragmentAllowedCharacterSet]);
		}

		if ((tmp = strchr(UTF8String, '?')) != NULL) {
			*tmp = '\0';
			_percentEncodedQuery = [[OFString alloc]
			_percentEncodedPath = [path copy];
			_percentEncodedQuery = [query copy];
		} else {
			if (path.length == 0) {
				_percentEncodedPath =
				    [URI->_percentEncodedPath copy];
				_percentEncodedQuery = (query != nil
			    initWithUTF8String: tmp + 1];

			OFURIVerifyIsEscaped(_percentEncodedQuery,
				    ? [query copy]
				    : [URI->_percentEncodedQuery copy]);
			    [OFCharacterSet URIQueryAllowedCharacterSet]);
		}

			} else {
				if ([path hasPrefix: @"/"])
		if (*UTF8String == '/')
			_percentEncodedPath = [[OFString alloc]
					_percentEncodedPath = [path copy];
			    initWithUTF8String: UTF8String];
		else {
				else
			OFString *relativePath =
			    [OFString stringWithUTF8String: UTF8String];

			if ([URI->_percentEncodedPath hasSuffix: @"/"])
				_percentEncodedPath = [[URI->_percentEncodedPath
					_percentEncodedPath = [merge(
					    URI->_percentEncodedPath, path)
				    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];
				_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