ObjFW  Check-in [d94375547e]

Overview
Comment:OFURL: Properly handle escaping / unescaping
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d94375547e1a99251c867a93a4cb6681773264646c23d8a04a4c35923fb69899
User & Date: js on 2015-06-30 20:56:45
Other Links: manifest | tags
Context
2015-07-02
20:41
OFURL: Fix handling of scheme in -[string] check-in: bdf4b1d37e user: js tags: trunk
2015-06-30
20:56
OFURL: Properly handle escaping / unescaping check-in: d94375547e user: js tags: trunk
19:56
Add -[stringByURLEncodingWithIgnoredCharacters:] check-in: 7b2f48cf71 user: js tags: trunk
Changes

Modified src/OFURL.m from [a3f2325841] to [71a208e68b].

50
51
52
53
54
55
56

57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74



75
76
77
78
79
80




81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102




103
104
105


106
107
108
109
110
111
112
113
114
115
116
117
118


119
120
121
122
123
124
125
126
127
128
129
130
131


132
133
134
135
136
137
138
139
140
141
142
143
144
145
146


147
148
149
150
151
152
153


154
155
156
157
158
159
160


161
162
163
164


165


166
167
168
169
170
171
172
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72



73
74
75
76
77
78
79


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101




102
103
104
105
106


107
108
109
110
111
112
113
114
115
116
117
118
119


120
121
122
123
124
125
126
127
128
129
130
131
132


133
134
135
136
137
138
139
140
141
142
143
144
145
146
147


148
149
150
151
152
153
154


155
156
157
158
159
160
161


162
163
164
165


166
167
168
169
170
171
172
173
174
175
176
177







+















-
-
-
+
+
+




-
-
+
+
+
+


















-
-
-
-
+
+
+
+

-
-
+
+











-
-
+
+











-
-
+
+













-
-
+
+





-
-
+
+





-
-
+
+


-
-
+
+

+
+







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

	self = [super init];

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

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

		UTF8String = UTF8String2;

		if ((tmp = strstr(UTF8String, "://")) == NULL)
			@throw [OFInvalidFormatException exception];

		for (tmp2 = UTF8String; tmp2 < tmp; tmp2++)
			*tmp2 = tolower((int)*tmp2);

		_scheme = [[OFString alloc]
		    initWithUTF8String: UTF8String
				length: tmp - UTF8String];
		_scheme = [[[OFString stringWithUTF8String: UTF8String
						    length: tmp - UTF8String]
		    stringByURLDecoding] copy];

		UTF8String = tmp + 3;

		if ([_scheme isEqual: @"file"]) {
			_path = [[OFString alloc]
			    initWithUTF8String: UTF8String];
			_path = [[[OFString stringWithUTF8String:
			    UTF8String] stringByURLDecoding] copy];

			objc_autoreleasePoolPop(pool);
			return self;
		}

		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];
				_user = [[[OFString stringWithUTF8String:
				    UTF8String] stringByURLDecoding] copy];
				_password = [[[OFString stringWithUTF8String:
				    tmp3] stringByURLDecoding] copy];
			} else
				_user = [[OFString alloc]
				    initWithUTF8String: UTF8String];
				_user = [[[OFString stringWithUTF8String:
				    UTF8String] stringByURLDecoding] copy];

			UTF8String = tmp2;
		}

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

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

			_host = [[OFString alloc]
			    initWithUTF8String: UTF8String];
			_host = [[[OFString stringWithUTF8String:
			    UTF8String] stringByURLDecoding] copy];

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

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

			_port = [portString decimalValue];

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

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

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

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

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

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

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

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

			_path = [[OFString alloc]
			    initWithUTF8String: UTF8String];
			_path = [[[OFString stringWithUTF8String:
			    UTF8String] stringByURLDecoding] copy];
		}

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

180
181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205


206
207
208
209
210


211
212
213
214
215
216


217
218
219
220
221


222
223
224

225

226

227
228
229
230

231
232
233

234
235

236
237
238

239
240


241
242
243
244
245
246
247
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209


210
211
212
213
214
215

216
217
218
219
220
221


222
223
224
225
226


227
228
229


230
231
232

233
234
235


236

237

238
239

240
241
242

243


244
245
246
247
248
249
250
251
252







+

















-
-
+
+




-
+
+




-
-
+
+



-
-
+
+

-
-
+

+
-
+


-
-
+
-

-
+

-
+


-
+
-
-
+
+








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

	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		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];
			_fragment = [[[OFString stringWithUTF8String:
			    tmp + 1] stringByURLDecoding] copy];
		}

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

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

		if (*UTF8String == '/')
			_path = [[OFString alloc]
			    initWithUTF8String: UTF8String + 1];
			_path = [[[OFString stringWithUTF8String:
			    UTF8String + 1] stringByURLDecoding] copy];
		else {
			void *pool;
			OFString *s;
			OFString *path, *s;

			path = [[[OFString stringWithUTF8String:
			pool = objc_autoreleasePoolPush();
			    UTF8String] stringByURLDecoding] copy];

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

			_path = [[s stringByStandardizingURLPath] copy];

		}
			objc_autoreleasePoolPop(pool);
		}

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

449
450
451
452
453
454
455
456
457




458
459
460
461


462

463
464
465
466
467



468
469

470
471
472

473
474
475
476
477
478
479
480


481
482
483

484
485
486

487
488
489



490
491
492
493
494
495
496
454
455
456
457
458
459
460


461
462
463
464
465
466
467

468
469
470
471
472
473
474
475

476
477
478
479

480
481
482

483
484
485
486
487
488
489
490

491
492
493
494

495
496
497

498
499
500

501
502
503
504
505
506
507
508
509
510







-
-
+
+
+
+



-
+
+

+




-
+
+
+

-
+


-
+







-
+
+


-
+


-
+


-
+
+
+







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

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

	[ret appendFormat: @"%@://", [_scheme stringByURLEncoding]];

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

		objc_autoreleasePoolPop(pool);
		return ret;
	}

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

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

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

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

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

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

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

	objc_autoreleasePoolPop(pool);

	[ret makeImmutable];

	return ret;
}

- (OFString*)description

Modified tests/OFURLTests.m from [b32681805b] to [47061d302f].

21
22
23
24
25
26
27
28


29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63


64
65
66


67
68

69

70
71
72


73
74

75
76
77
78
79
80
81
21
22
23
24
25
26
27

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62


63
64
65


66
67
68
69
70

71
72


73
74
75

76
77
78
79
80
81
82
83







-
+
+
















-
+
















-
-
+
+

-
-
+
+


+
-
+

-
-
+
+

-
+







#import "OFAutoreleasePool.h"

#import "OFInvalidFormatException.h"

#import "TestsAppDelegate.h"

static OFString *module = @"OFURL";
static OFString *url_str = @"http://u:p@h:1234/f;p?q#f";
static OFString *url_str = @"ht%3Atp://us%3Aer:p%40w@ho%3Ast:1234/"
    @"pa%3Bth;pa%3Fram?que%23ry#frag%23ment";

@implementation TestsAppDelegate (OFURLTests)
- (void)URLTests
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFURL *u1, *u2, *u3, *u4;

	TEST(@"+[URLWithString:]",
	    R(u1 = [OFURL URLWithString: url_str]) &&
	    R(u2 = [OFURL URLWithString: @"http://foo:80"]) &&
	    R(u3 = [OFURL URLWithString: @"http://bar/"]) &&
	    R(u4 = [OFURL URLWithString: @"file:///etc/passwd"]))

	TEST(@"+[URLWithString:relativeToURL:]",
	    [[[OFURL URLWithString: @"/foo"
		     relativeToURL: u1] string] isEqual:
	    @"http://u:p@h:1234/foo"] &&
	    @"ht%3Atp://us%3Aer:p%40w@ho%3Ast:1234/foo"] &&
	    [[[OFURL URLWithString: @"foo/bar?q"
		     relativeToURL: [OFURL URLWithString: @"http://h/qux/quux"]]
	    string] isEqual: @"http://h/qux/foo/bar?q"] &&
	    [[[OFURL URLWithString: @"foo/bar"
		     relativeToURL: [OFURL URLWithString: @"http://h/qux/?x"]]
	    string] isEqual: @"http://h/qux/foo/bar"] &&
	    [[[OFURL URLWithString: @"http://foo/?q"
		     relativeToURL: u1] string] isEqual: @"http://foo/?q"])

	TEST(@"-[string]",
	    [[u1 string] isEqual: url_str] &&
	    [[u2 string] isEqual: @"http://foo"] &&
	    [[u3 string] isEqual: @"http://bar/"] &&
	    [[u4 string] isEqual: @"file:///etc/passwd"])

	TEST(@"-[scheme]",
	    [[u1 scheme] isEqual: @"http"] && [[u4 scheme] isEqual: @"file"])
	TEST(@"-[user]", [[u1 user] isEqual: @"u"] && [u4 user] == nil)
	    [[u1 scheme] isEqual: @"ht%3Atp"] && [[u4 scheme] isEqual: @"file"])
	TEST(@"-[user]", [[u1 user] isEqual: @"us:er"] && [u4 user] == nil)
	TEST(@"-[password]",
	    [[u1 password] isEqual: @"p"] && [u4 password] == nil)
	TEST(@"-[host]", [[u1 host] isEqual: @"h"] && [u4 port] == 0)
	    [[u1 password] isEqual: @"p@w"] && [u4 password] == nil)
	TEST(@"-[host]", [[u1 host] isEqual: @"ho:st"] && [u4 port] == 0)
	TEST(@"-[port]", [u1 port] == 1234)
	TEST(@"-[path]",
	    [[u1 path] isEqual: @"pa;th"] &&
	    [[u1 path] isEqual: @"f"] && [[u4 path] isEqual: @"/etc/passwd"])
	    [[u4 path] isEqual: @"/etc/passwd"])
	TEST(@"-[parameters]",
	    [[u1 parameters] isEqual: @"p"] && [u4 parameters] == nil)
	TEST(@"-[query]", [[u1 query] isEqual: @"q"] && [u4 query] == nil)
	    [[u1 parameters] isEqual: @"pa?ram"] && [u4 parameters] == nil)
	TEST(@"-[query]", [[u1 query] isEqual: @"que#ry"] && [u4 query] == nil)
	TEST(@"-[fragment]",
	    [[u1 fragment] isEqual: @"f"] && [u4 fragment] == nil)
	    [[u1 fragment] isEqual: @"frag#ment"] && [u4 fragment] == nil)

	TEST(@"-[copy]", R(u4 = [[u1 copy] autorelease]))

	TEST(@"-[isEqual:]", [u1 isEqual: u4] && ![u2 isEqual: u3] &&
	    [[OFURL URLWithString: @"HTTP://bar/"] isEqual: u3])

	TEST(@"-[hash:]", [u1 hash] == [u4 hash] && [u2 hash] != [u3 hash])