xref: /haiku/src/tests/kits/net/service/UrlTest.cpp (revision 6da9451ebaec75b9c1166e822cbfac4c819504dc)
1 /*
2  * Copyright 2010, Christophe Huriaux
3  * Copyright 2014, Haiku, inc.
4  * Distributed under the terms of the MIT licence
5  */
6 
7 
8 #include "UrlTest.h"
9 
10 
11 #include <cstdlib>
12 #include <cstring>
13 #include <cstdio>
14 
15 #include <NetworkKit.h>
16 
17 #include <cppunit/TestCaller.h>
18 #include <cppunit/TestSuite.h>
19 
20 
21 UrlTest::UrlTest()
22 {
23 }
24 
25 
26 UrlTest::~UrlTest()
27 {
28 }
29 
30 
31 // Test that parsing a valid URL and converting back to string doesn't alter it
32 void UrlTest::ParseTest()
33 {
34 	uint8 testIndex;
35 	BUrl testUrl;
36 
37 	const char* kTestLength[] =
38 	{
39 		"http://user:pass@www.foo.com:80/path?query#fragment",
40 		"http://user:pass@www.foo.com:80/path?query#",
41 		"http://user:pass@www.foo.com:80/path?query",
42 		"http://user:pass@www.foo.com:80/path?",
43 		"http://user:pass@www.foo.com:80/path",
44 		"http://user:pass@www.foo.com:80/",
45 		"http://user:pass@www.foo.com",
46 		"http://user:pass@",
47 		"http://www.foo.com",
48 		"http://",
49 		"http:"
50 	};
51 
52 	for (testIndex = 0; testIndex < sizeof(kTestLength) / sizeof(const char*);
53 		testIndex++)
54 	{
55 		NextSubTest();
56 
57 		testUrl.SetUrlString(kTestLength[testIndex]);
58 		CPPUNIT_ASSERT_EQUAL(BString(kTestLength[testIndex]),
59 			testUrl.UrlString());
60 	}
61 }
62 
63 
64 void UrlTest::TestIsValid()
65 {
66 	BUrl url("http:");
67 	CPPUNIT_ASSERT_MESSAGE("Created with a scheme but no hierarchical segment.",
68 		!url.IsValid());
69 
70 	url.SetHost("<invalid>");
71 	CPPUNIT_ASSERT_MESSAGE("Set to an invalid host.", !url.IsValid());
72 
73 	url.SetUrlString("");
74 	url.SetProtocol("\t \n");
75 	CPPUNIT_ASSERT_MESSAGE("Set a protocol with whitespace", !url.IsValid());
76 	url.SetProtocol("123");
77 	CPPUNIT_ASSERT_MESSAGE("Set an all-digits protocol", !url.IsValid());
78 
79 	url.SetUserName("user");
80 	CPPUNIT_ASSERT_MESSAGE("Retain invalid state on user change",
81 		!url.IsValid());
82 	url.SetPassword("pass");
83 	CPPUNIT_ASSERT_MESSAGE("Retain invalid state on password change",
84 		!url.IsValid());
85 
86 	url.SetProtocol("http");
87 	url.SetFragment("fragment");
88 	CPPUNIT_ASSERT_MESSAGE("Only protocol and fragment are set",
89 		!url.IsValid());
90 	url.SetFragment("fragment");
91 	url.SetProtocol("http");
92 	CPPUNIT_ASSERT_MESSAGE("Only protocol and fragment are set",
93 		!url.IsValid());
94 }
95 
96 
97 void UrlTest::TestGettersSetters()
98 {
99 	BUrl url;
100 	url.SetProtocol("http");
101 	url.SetUserName("user");
102 	url.SetPassword("password");
103 	url.SetHost("example.com");
104 	url.SetPort(8080);
105 	url.SetPath("/path");
106 	url.SetRequest("query=value");
107 	url.SetFragment("fragment");
108 
109 	CPPUNIT_ASSERT_EQUAL(BString("http"), url.Protocol());
110 	CPPUNIT_ASSERT_EQUAL(BString("user"), url.UserName());
111 	CPPUNIT_ASSERT_EQUAL(BString("password"), url.Password());
112 	CPPUNIT_ASSERT_EQUAL(BString("user:password"), url.UserInfo());
113 	CPPUNIT_ASSERT_EQUAL(BString("example.com"), url.Host());
114 	CPPUNIT_ASSERT_EQUAL(BString("user:password@example.com:8080"),
115 		url.Authority());
116 	CPPUNIT_ASSERT_EQUAL(8080, url.Port());
117 	CPPUNIT_ASSERT_EQUAL(BString("/path"), url.Path());
118 	CPPUNIT_ASSERT_EQUAL(BString("query=value"), url.Request());
119 	CPPUNIT_ASSERT_EQUAL(BString("fragment"), url.Fragment());
120 	CPPUNIT_ASSERT_EQUAL(BString(
121 			"http://user:password@example.com:8080/path?query=value#fragment"),
122 		url.UrlString());
123 }
124 
125 
126 void UrlTest::TestNullity()
127 {
128 	BUrl url;
129 	url.SetProtocol("http");
130 	url.SetHost("example.com");
131 
132 	CPPUNIT_ASSERT(url.HasAuthority());
133 	CPPUNIT_ASSERT(url.HasHost());
134 
135 	CPPUNIT_ASSERT(!url.HasUserName());
136 	CPPUNIT_ASSERT(!url.HasPassword());
137 	CPPUNIT_ASSERT(!url.HasUserInfo());
138 	CPPUNIT_ASSERT(!url.HasPort());
139 	CPPUNIT_ASSERT(!url.HasPath());
140 	CPPUNIT_ASSERT(!url.HasRequest());
141 	CPPUNIT_ASSERT(!url.HasFragment());
142 }
143 
144 
145 void UrlTest::TestCopy()
146 {
147 	BUrl url1("http://example.com");
148 	BUrl url2(url1);
149 
150 	url2.SetHost("www.example.com");
151 
152 	CPPUNIT_ASSERT_EQUAL(BString("www.example.com"), url2.Host());
153 	CPPUNIT_ASSERT_EQUAL(BString("http://www.example.com"), url2.UrlString());
154 	CPPUNIT_ASSERT_EQUAL(BString("example.com"), url1.Host());
155 	CPPUNIT_ASSERT_EQUAL(BString("http://example.com"), url1.UrlString());
156 }
157 
158 
159 typedef struct
160 {
161 	const char* url;
162 
163 	struct
164 	{
165 		const char* protocol;
166 		const char* userName;
167 		const char* password;
168 		const char* host;
169 		int16		port;
170 		const char* path;
171 		const char* request;
172 		const char* fragment;
173 	} expected;
174 } ExplodeTest;
175 
176 
177 const ExplodeTest	kTestExplode[] =
178 	//  Url
179 	//       Protocol     User  Password  Hostname  Port     Path    Request      Fragment
180 	//       -------- --------- --------- --------- ---- ---------- ---------- ------------
181 	{
182 		{ "http://user:pass@host:80/path?query#fragment",
183 			{ "http",   "user",   "pass",   "host",	 80,   "/path",	  "query",   "fragment" } },
184 		{ "http://www.host.tld/path?query#fragment",
185 			{ "http",   "",        "", "www.host.tld",0,   "/path",	  "query",   "fragment" } },
186 		{ "",
187 			{ "",       "",        "", "",            0,   "",        "",        ""} },
188 		{ "mailto:John.Doe@example.com",
189 			{ "mailto", "",        "", "",            0,   "John.Doe@example.com", "", "" } },
190 		{ "mailto:?to=addr1@an.example,addr2@an.example",
191 			{ "mailto", "",        "", "",            0,   "",        "to=addr1@an.example,addr2@an.example", "" } },
192 		{ "urn:oasis:names:specification:docbook:dtd:xml:4.1.2",
193 			{ "urn",    "",        "", "",            0,   "oasis:names:specification:docbook:dtd:xml:4.1.2", "", "" } },
194 		{ "http://www.goodsearch.com/login?return_path=/",
195 			{ "http",   "",        "", "www.goodsearch.com", 0, "/login", "return_path=/", "" } },
196 		{ "ldap://[2001:db8::7]/c=GB?objectClass?one",
197 			{ "ldap",   "",        "", "[2001:db8::7]",389,"/c=GB",   "objectClass?one", "" } },
198 		{ "HTTP://example.com.:80/%70a%74%68?a=%31#1%323",
199 			{ "HTTP",   "",        "", "example.com.",80,  "/%70a%74%68","a=%31","1%323"} }
200 	};
201 
202 void UrlTest::ExplodeImplodeTest()
203 {
204 	uint8 testIndex;
205 	BUrl testUrl;
206 
207 	for (testIndex = 0; testIndex < (sizeof(kTestExplode) / sizeof(ExplodeTest)); testIndex++)
208 	{
209 		NextSubTest();
210 		testUrl.SetUrlString(kTestExplode[testIndex].url);
211 
212 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].url),
213 			BString(testUrl.UrlString()));
214 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.protocol),
215 			BString(testUrl.Protocol()));
216 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.userName),
217 			BString(testUrl.UserName()));
218 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.password),
219 			BString(testUrl.Password()));
220 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.host),
221 			BString(testUrl.Host()));
222 		CPPUNIT_ASSERT_EQUAL(kTestExplode[testIndex].expected.port,
223 			testUrl.Port());
224 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.path),
225 			BString(testUrl.Path()));
226 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.request),
227 			BString(testUrl.Request()));
228 		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.fragment),
229 			BString(testUrl.Fragment()));
230 	}
231 }
232 
233 
234 void
235 UrlTest::PathOnly()
236 {
237 	BUrl test = "lol";
238 	CPPUNIT_ASSERT(test.HasPath());
239 	CPPUNIT_ASSERT_EQUAL(BString("lol"), test.Path());
240 	CPPUNIT_ASSERT_EQUAL(BString("lol"), test.UrlString());
241 }
242 
243 
244 void
245 UrlTest::RelativeUriTest()
246 {
247 	// http://skew.org/uri/uri%5Ftests.html
248 	struct RelativeUrl {
249 		const char* base;
250 		const char* relative;
251 		const char* absolute;
252 	};
253 
254 	const RelativeUrl tests[] = {
255 		// The port must be preserved
256 		{"http://host:81/",		"/path",				"http://host:81/path"},
257 
258 		// Tests from http://skew.org/uri/uri_tests.html
259 		{"http://example.com/path?query#frag", "",
260 			"http://example.com/path?query"},
261 			// The fragment must be dropped when changing the path, but the
262 			// query must be preserved
263 		{"foo:a/b",				"../c",					"foo:/c"},
264 			// foo:c would be more intuitive, and is what skew.org tests.
265 			// However, foo:/c is what the RFC says we should get.
266 		{"foo:a",				"foo:.",				"foo:"},
267 		{"zz:abc",				"/foo/../../../bar",	"zz:/bar"},
268 		{"zz:abc",				"/foo/../bar",			"zz:/bar"},
269 		{"zz:abc",				"foo/../../../bar",		"zz:/bar"},
270 			// zz:bar would be more intuitive, ...
271 		{"zz:abc",				"zz:.",					"zz:"},
272 		{"http://a/b/c/d;p?q",	"/.",					"http://a/"},
273 		{"http://a/b/c/d;p?q",	"/.foo",				"http://a/.foo"},
274 		{"http://a/b/c/d;p?q",	".foo",					"http://a/b/c/.foo"},
275 		{"http://a/b/c/d;p?q",	"g:h",					"g:h"},
276 
277 		{"http://a/b/c/d;p?q",	"g:h",					"g:h"},
278 		{"http://a/b/c/d;p?q",	"g",					"http://a/b/c/g"},
279 		{"http://a/b/c/d;p?q",	"./g",					"http://a/b/c/g"},
280 		{"http://a/b/c/d;p?q",	"g/",					"http://a/b/c/g/"},
281 		{"http://a/b/c/d;p?q",	"/g",					"http://a/g"},
282 		{"http://a/b/c/d;p?q",	"//g",					"http://g"},
283 		{"http://a/b/c/d;p?q",	"?y",					"http://a/b/c/d;p?y"},
284 		{"http://a/b/c/d;p?q",	"g?y",					"http://a/b/c/g?y"},
285 		{"http://a/b/c/d;p?q",	"#s",					"http://a/b/c/d;p?q#s"},
286 		{"http://a/b/c/d;p?q",	"g#s",					"http://a/b/c/g#s"},
287 		{"http://a/b/c/d;p?q",	"g?y#s",				"http://a/b/c/g?y#s"},
288 		{"http://a/b/c/d;p?q",	";x",					"http://a/b/c/;x"},
289 		{"http://a/b/c/d;p?q",	"g;x",					"http://a/b/c/g;x"},
290 		{"http://a/b/c/d;p?q",	"g;x?y#s",				"http://a/b/c/g;x?y#s"},
291 		{"http://a/b/c/d;p?q",	"",						"http://a/b/c/d;p?q"},
292 		{"http://a/b/c/d;p?q",	".",					"http://a/b/c/"},
293 		{"http://a/b/c/d;p?q",	"./",					"http://a/b/c/"},
294 		{"http://a/b/c/d;p?q",	"..",					"http://a/b/"},
295 		{"http://a/b/c/d;p?q",	"../",					"http://a/b/"},
296 		{"http://a/b/c/d;p?q",	"../g",					"http://a/b/g"},
297 		{"http://a/b/c/d;p?q",	"../..",				"http://a/"},
298 		{"http://a/b/c/d;p?q",	"../../",				"http://a/"},
299 		{"http://a/b/c/d;p?q",	"../../g",				"http://a/g"},
300 
301 		// Parsers must be careful in handling cases where there are more
302 		// relative path ".." segments than there are hierarchical levels in the
303 		// base URI's path. Note that the ".." syntax cannot be used to change
304 		// the authority component of a URI.
305 		{"http://a/b/c/d;p?q",	"../../../g",			"http://a/g"},
306 		{"http://a/b/c/d;p?q",	"../../../../g",		"http://a/g"},
307 
308 		// Similarly, parsers must remove the dot-segments "." and ".." when
309 		// they are complete components of a path, but not when they are only
310 		// part of a segment.
311 		{"http://a/b/c/d;p?q",	"/./g",					"http://a/g"},
312 		{"http://a/b/c/d;p?q",	"/../g",				"http://a/g"},
313 		{"http://a/b/c/d;p?q",	"g.",					"http://a/b/c/g."},
314 		{"http://a/b/c/d;p?q",	".g",					"http://a/b/c/.g"},
315 		{"http://a/b/c/d;p?q",	"g..",					"http://a/b/c/g.."},
316 		{"http://a/b/c/d;p?q",	"..g",					"http://a/b/c/..g"},
317 
318 		// Less likely are cases where the relative URI reference uses
319 		// unnecessary or nonsensical forms of the "." and ".." complete path
320 		// segments.
321 		{"http://a/b/c/d;p?q",	"./../g",				"http://a/b/g"},
322 		{"http://a/b/c/d;p?q",	"./g/.",				"http://a/b/c/g/"},
323 		{"http://a/b/c/d;p?q",	"g/./h",				"http://a/b/c/g/h"},
324 		{"http://a/b/c/d;p?q",	"g/../h",				"http://a/b/c/h"},
325 		{"http://a/b/c/d;p?q",	"g;x=1/./y",			"http://a/b/c/g;x=1/y"},
326 		{"http://a/b/c/d;p?q",	"g;x=1/../y",			"http://a/b/c/y"},
327 
328 		// Some applications fail to separate the reference's query and/or
329 		// fragment components from a relative path before merging it with the
330 		// base path and removing dot-segments. This error is rarely noticed,
331 		// since typical usage of a fragment never includes the hierarchy ("/")
332 		// character, and the query component is not normally used within
333 		// relative references.
334 		{"http://a/b/c/d;p?q",	"g?y/./x",			"http://a/b/c/g?y/./x"},
335 		{"http://a/b/c/d;p?q",	"g?y/../x",			"http://a/b/c/g?y/../x"},
336 		{"http://a/b/c/d;p?q",	"g#s/./x",			"http://a/b/c/g#s/./x"},
337 		{"http://a/b/c/d;p?q",	"g#s/../x",			"http://a/b/c/g#s/../x"},
338 
339 		// Some parsers allow the scheme name to be present in a relative URI
340 		// reference if it is the same as the base URI scheme. This is
341 		// considered to be a loophole in prior specifications of partial URI
342 		// [RFC1630]. Its use should be avoided, but is allowed for backward
343 		// compatibility.
344 		{"http://a/b/c/d;p?q",	"http:g",			"http:g"},
345 		{"http://a/b/c/d;p?q",	"http:",			"http:"},
346 
347 		{"http://a/b/c/d;p?q",	"./g:h",			"http://a/b/c/g:h"},
348 		{"http://a/b/c/d;p?q",	"/a/b/c/./../../g",	"http://a/a/g"},
349 
350 		{"http://a/b/c/d;p?q=1/2",	"g",			"http://a/b/c/g"},
351 		{"http://a/b/c/d;p?q=1/2",	"./g",			"http://a/b/c/g"},
352 		{"http://a/b/c/d;p?q=1/2",	"g/",			"http://a/b/c/g/"},
353 		{"http://a/b/c/d;p?q=1/2",	"/g",			"http://a/g"},
354 		{"http://a/b/c/d;p?q=1/2",	"//g",			"http://g"},
355 		{"http://a/b/c/d;p?q=1/2",	"?y",			"http://a/b/c/d;p?y"},
356 		{"http://a/b/c/d;p?q=1/2",	"g?y",			"http://a/b/c/g?y"},
357 		{"http://a/b/c/d;p?q=1/2",	"g?y/./x",		"http://a/b/c/g?y/./x"},
358 		{"http://a/b/c/d;p?q=1/2",	"g?y/../x",		"http://a/b/c/g?y/../x"},
359 		{"http://a/b/c/d;p?q=1/2",	"g#s",			"http://a/b/c/g#s"},
360 		{"http://a/b/c/d;p?q=1/2",	"g#s/./x",		"http://a/b/c/g#s/./x"},
361 		{"http://a/b/c/d;p?q=1/2",	"g#s/../x",		"http://a/b/c/g#s/../x"},
362 		{"http://a/b/c/d;p?q=1/2",	"./",			"http://a/b/c/"},
363 		{"http://a/b/c/d;p?q=1/2",	"../",			"http://a/b/"},
364 		{"http://a/b/c/d;p?q=1/2",	"../g",			"http://a/b/g"},
365 		{"http://a/b/c/d;p?q=1/2",	"../../",		"http://a/"},
366 		{"http://a/b/c/d;p?q=1/2",	"../../g",		"http://a/g"},
367 
368 		{"http://a/b/c/d;p=1/2?q",	"g",			"http://a/b/c/d;p=1/g"},
369 		{"http://a/b/c/d;p=1/2?q",	"./g",			"http://a/b/c/d;p=1/g"},
370 		{"http://a/b/c/d;p=1/2?q",	"g/",			"http://a/b/c/d;p=1/g/"},
371 		{"http://a/b/c/d;p=1/2?q",	"g?y",			"http://a/b/c/d;p=1/g?y"},
372 		{"http://a/b/c/d;p=1/2?q",	";x",			"http://a/b/c/d;p=1/;x"},
373 		{"http://a/b/c/d;p=1/2?q",	"g;x",			"http://a/b/c/d;p=1/g;x"},
374 		{"http://a/b/c/d;p=1/2?q", "g;x=1/./y", "http://a/b/c/d;p=1/g;x=1/y"},
375 		{"http://a/b/c/d;p=1/2?q",	"g;x=1/../y",	"http://a/b/c/d;p=1/y"},
376 		{"http://a/b/c/d;p=1/2?q",	"./",			"http://a/b/c/d;p=1/"},
377 		{"http://a/b/c/d;p=1/2?q",	"../",			"http://a/b/c/"},
378 		{"http://a/b/c/d;p=1/2?q",	"../g",			"http://a/b/c/g"},
379 		{"http://a/b/c/d;p=1/2?q",	"../../",		"http://a/b/"},
380 		{"http://a/b/c/d;p=1/2?q",	"../../g",		"http://a/b/g"},
381 
382 		// Empty host and directory
383 		{"fred:///s//a/b/c",	"g:h",					"g:h"},
384 		{"fred:///s//a/b/c",	"g",					"fred:///s//a/b/g"},
385 		{"fred:///s//a/b/c",	"./g",					"fred:///s//a/b/g"},
386 		{"fred:///s//a/b/c",	"g/",					"fred:///s//a/b/g/"},
387 		{"fred:///s//a/b/c",	"/g",					"fred:///g"},
388 		{"fred:///s//a/b/c",	"//g",					"fred://g"},
389 		{"fred:///s//a/b/c",	"//g/x",				"fred://g/x"},
390 		{"fred:///s//a/b/c",	"///g",					"fred:///g"},
391 		{"fred:///s//a/b/c",	"./",					"fred:///s//a/b/"},
392 		{"fred:///s//a/b/c",	"../",					"fred:///s//a/"},
393 		{"fred:///s//a/b/c",	"../g",					"fred:///s//a/g"},
394 		{"fred:///s//a/b/c",	"../..",				"fred:///s//"},
395 		{"fred:///s//a/b/c",	"../../g",				"fred:///s//g"},
396 		{"fred:///s//a/b/c",	"../../../g",			"fred:///s/g"},
397 		{"fred:///s//a/b/c",	"../../../g",			"fred:///s/g"},
398 
399 		{"http:///s//a/b/c",	"g:h",					"g:h"},
400 		{"http:///s//a/b/c",	"g",					"http:///s//a/b/g"},
401 		{"http:///s//a/b/c",	"./g",					"http:///s//a/b/g"},
402 		{"http:///s//a/b/c",	"g/",					"http:///s//a/b/g/"},
403 		{"http:///s//a/b/c",	"/g",					"http:///g"},
404 		{"http:///s//a/b/c",	"//g",					"http://g"},
405 		{"http:///s//a/b/c",	"//g/x",				"http://g/x"},
406 		{"http:///s//a/b/c",	"///g",					"http:///g"},
407 		{"http:///s//a/b/c",	"./",					"http:///s//a/b/"},
408 		{"http:///s//a/b/c",	"../",					"http:///s//a/"},
409 		{"http:///s//a/b/c",	"../g",					"http:///s//a/g"},
410 		{"http:///s//a/b/c",	"../..",				"http:///s//"},
411 		{"http:///s//a/b/c",	"../../g",				"http:///s//g"},
412 		{"http:///s//a/b/c",	"../../../g",			"http:///s/g"},
413 		{"http:///s//a/b/c",	"../../../g",			"http:///s/g"},
414 
415 		{"foo:xyz",				"bar:abc",				"bar:abc"},
416 		{"http://example/x/y/z","../abc",				"http://example/x/abc"},
417 		{"http://example2/x/y/z","http://example/x/abc","http://example/x/abc"},
418 		{"http://ex/x/y/z",		"../r",					"http://ex/x/r"},
419 		{"http://ex/x/y",		"q/r",					"http://ex/x/q/r"},
420 		{"http://ex/x/y",		"q/r#s",				"http://ex/x/q/r#s"},
421 		{"http://ex/x/y",		"q/r#s/t",				"http://ex/x/q/r#s/t"},
422 		{"http://ex/x/y",		"ftp://ex/x/q/r",		"ftp://ex/x/q/r"},
423 		{"http://ex/x/y",		"",						"http://ex/x/y"},
424 		{"http://ex/x/y/",		"",						"http://ex/x/y/"},
425 		{"http://ex/x/y/pdq",	"",						"http://ex/x/y/pdq"},
426 		{"http://ex/x/y/",		"z/",					"http://ex/x/y/z/"},
427 
428 		{"file:/swap/test/animal.rdf", "#Animal",
429 			"file:/swap/test/animal.rdf#Animal"},
430 		{"file:/e/x/y/z",		"../abc",				"file:/e/x/abc"},
431 		{"file:/example2/x/y/z","/example/x/abc",		"file:/example/x/abc"},
432 		{"file:/e/x/y/z",		"../r",					"file:/e/x/r"},
433 		{"file:/e/x/y/z",		"/r",					"file:/r"},
434 		{"file:/e/x/y",			"q/r",					"file:/e/x/q/r"},
435 		{"file:/e/x/y",			"q/r#s",				"file:/e/x/q/r#s"},
436 		{"file:/e/x/y",			"q/r#",					"file:/e/x/q/r#"},
437 		{"file:/e/x/y",			"q/r#s/t",				"file:/e/x/q/r#s/t"},
438 		{"file:/e/x/y",			"ftp://ex/x/q/r",		"ftp://ex/x/q/r"},
439 		{"file:/e/x/y",			"",						"file:/e/x/y"},
440 		{"file:/e/x/y/",		"",						"file:/e/x/y/"},
441 		{"file:/e/x/y/pdq",		"",						"file:/e/x/y/pdq"},
442 		{"file:/e/x/y/",		"z/",					"file:/e/x/y/z/"},
443 		{"file:/devel/WWW/2000/10/swap/test/reluri-1.n3",
444 			"file://meetings.example.com/cal#m1",
445 			"file://meetings.example.com/cal#m1"},
446 		{"file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3",
447 			"file://meetings.example.com/cal#m1",
448 			"file://meetings.example.com/cal#m1"},
449 		{"file:/some/dir/foo",		"./#blort",		"file:/some/dir/#blort"},
450 		{"file:/some/dir/foo",		"./#",			"file:/some/dir/#"},
451 
452 		{"http://example/x/abc.efg", "./",			"http://example/x/"},
453 		{"http://example2/x/y/z", "//example/x/abc","http://example/x/abc"},
454 		{"http://ex/x/y/z",			"/r",			"http://ex/r"},
455 		{"http://ex/x/y",			"./q:r",		"http://ex/x/q:r"},
456 		{"http://ex/x/y",			"./p=q:r",		"http://ex/x/p=q:r"},
457 		{"http://ex/x/y?pp/qq",		"?pp/rr",		"http://ex/x/y?pp/rr"},
458 		{"http://ex/x/y?pp/qq",		"y/z",			"http://ex/x/y/z"},
459 
460 		{"mailto:local", "local/qual@domain.org#frag",
461 			"mailto:local/qual@domain.org#frag"},
462 		{"mailto:local/qual1@domain1.org", "more/qual2@domain2.org#frag",
463 			"mailto:local/more/qual2@domain2.org#frag"},
464 
465 		{"http://ex/x/y?q",		"y?q",			"http://ex/x/y?q"},
466 		{"http://ex?p",			"x/y?q",		"http://ex/x/y?q"},
467 		{"foo:a/b",				"c/d",			"foo:a/c/d"},
468 		{"foo:a/b",				"/c/d",			"foo:/c/d"},
469 		{"foo:a/b?c#d",			"",				"foo:a/b?c"},
470 		{"foo:a",				"b/c",			"foo:b/c"},
471 		{"foo:/a/y/z",			"../b/c",		"foo:/a/b/c"},
472 		{"foo:a",				"./b/c",		"foo:b/c"},
473 		{"foo:a",				"/./b/c",		"foo:/b/c"},
474 		{"foo://a//b/c",		"../../d",		"foo://a/d"},
475 		{"foo:a",				".",			"foo:"},
476 		{"foo:a",				"..",			"foo:"},
477 
478 		{"http://example/x/y%2Fz", "abc",			"http://example/x/abc"},
479 		{"http://example/a/x/y/z", "../../x%2Fabc", "http://example/a/x%2Fabc"},
480 		{"http://example/a/x/y%2Fz", "../x%2Fabc", "http://example/a/x%2Fabc"},
481 		{"http://example/x%2Fy/z", "abc", "http://example/x%2Fy/abc"},
482 		{"http://ex/x/y", "q%3Ar", "http://ex/x/q%3Ar"},
483 		{"http://example/x/y%2Fz", "/x%2Fabc", "http://example/x%2Fabc"},
484 		{"http://example/x/y/z", "/x%2Fabc", "http://example/x%2Fabc"},
485 
486 		{"mailto:local1@domain1?query1", "local2@domain2",
487 			"mailto:local2@domain2"},
488 		{"mailto:local1@domain1", "local2@domain2?query2",
489 			"mailto:local2@domain2?query2"},
490 		{"mailto:local1@domain1?query1", "local2@domain2?query2",
491 			"mailto:local2@domain2?query2"},
492 		{"mailto:local@domain?query1", "?query2",
493 			"mailto:local@domain?query2"},
494 		{"mailto:?query1", "local2@domain2?query2",
495 			"mailto:local2@domain2?query2"},
496 		{"mailto:local1@domain1?query1", "?query2",
497 			"mailto:local1@domain1?query2"},
498 
499 		{"foo:bar", "http://example/a/b?c/../d", "http://example/a/b?c/../d"},
500 		{"foo:bar", "http://example/a/b#c/../d", "http://example/a/b#c/../d"},
501 		{"http://example.org/base/uri", "http:this", "http:this"},
502 		{"http:base", "http:this", "http:this"},
503 		{"f:/a", ".//g", "f://g"},
504 		{"f://example.org/base/a", "b/c//d/e", "f://example.org/base/b/c//d/e"},
505 		{"mid:m@example.ord/c@example.org", "m2@example.ord/c2@example.org",
506 			"mid:m@example.ord/m2@example.ord/c2@example.org"},
507 		{"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/", "mini1.xml",
508 			"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml"},
509 		{"foo:a/y/z", "../b/c", "foo:a/b/c"},
510 		{"foo:", "b", "foo:b"},
511 		{"foo://a", "b", "foo://a/b"},
512 		{"foo://a?q", "b", "foo://a/b"},
513 		{"foo://a", "b?q", "foo://a/b?q"},
514 		{"foo://a?r", "b?q", "foo://a/b?q"},
515 	};
516 
517 	BString message(" Base: ");
518 	for (unsigned int index = 0; index < sizeof(tests) / sizeof(RelativeUrl);
519 		index++)
520 	{
521 		NextSubTest();
522 
523 		BUrl baseUrl(tests[index].base);
524 
525 		message.Truncate(7, true);
526 		message << tests[index].base;
527 		message << " Relative: ";
528 		message << tests[index].relative;
529 
530 		CPPUNIT_ASSERT_EQUAL_MESSAGE(message.String(),
531 			BString(tests[index].absolute),
532 			BUrl(baseUrl, tests[index].relative).UrlString());
533 	}
534 }
535 
536 
537 void
538 UrlTest::IDNTest()
539 {
540 	// http://www.w3.org/2004/04/uri-rel-test.html
541 	// TODO We need to decide wether to store them as UTF-8 or IDNA/punycode.
542 
543 	struct Test {
544 		const char* escaped;
545 		const char* decoded;
546 	};
547 
548 	Test tests[] = {
549 		{ "http://www.w%33.org", "http://www.w3.org" },
550 		{ "http://r%C3%A4ksm%C3%B6rg%C3%A5s.josefsson.org",
551 			"http://xn--rksmrgs-5wao1o.josefsson.org" },
552 		{ "http://%E7%B4%8D%E8%B1%86.w3.mag.keio.ac.jp",
553 			"http://xn--99zt52a.w3.mag.keio.ac.jp" },
554 		{ "http://www.%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA"
555 			"%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3"
556 			"%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82"
557 			"%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0"
558 			"%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3"
559 			"%81%9F%E3%82%8A%E3%81%AA%E3%81%84.w3.mag.keio.ac.jp/",
560 			"http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9b"
561 			"ya3kc6lra.w3.mag.keio.ac.jp/" },
562 
563 		{ "http://%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA%E3"
564 			"%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3%82"
565 			"%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82%81"
566 			"%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0%E3"
567 			"%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81"
568 			"%9F%E3%82%8A%E3%81%AA%E3%81%84.%E3%81%BB%E3%82%93%E3%81%A8%E3%81"
569 			"%86%E3%81%AB%E3%81%AA%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE"
570 			"%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3"
571 			"%81%84%E3%82%93%E3%82%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82"
572 			"%8B%E3%81%BE%E3%81%A0%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA"
573 			"%E3%81%84%E3%81%A8%E3%81%9F%E3%82%8A%E3%81%AA%E3%81%84.%E3%81%BB"
574 			"%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA%E3%81%8C%E3%81%84%E3"
575 			"%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81"
576 			"%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82%81%E3%81%84%E3%81%AE"
577 			"%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0%E3%81%AA%E3%81%8C%E3"
578 			"%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81%9F%E3%82%8A%E3%81"
579 			"%AA%E3%81%84.w3.mag.keio.ac.jp/",
580 			"http://xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3k"
581 			"c6lra.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3kc6"
582 			"lra.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3kc6lr"
583 			"a.w3.mag.keio.ac.jp/" },
584 		{ NULL, NULL }
585 	};
586 
587 	for (int i = 0; tests[i].escaped != NULL; i++)
588 	{
589 		NextSubTest();
590 		BUrl url(tests[i].escaped);
591 		url.UrlDecode();
592 		CPPUNIT_ASSERT_EQUAL(BUrl(tests[i].decoded).UrlString(),
593 			url.UrlString());
594 	}
595 }
596 
597 
598 /* static */ void
599 UrlTest::AddTests(BTestSuite& parent)
600 {
601 	CppUnit::TestSuite& suite = *new CppUnit::TestSuite("UrlTest");
602 
603 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::ParseTest",
604 		&UrlTest::ParseTest));
605 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestIsValid",
606 		&UrlTest::TestIsValid));
607 	suite.addTest(new CppUnit::TestCaller<UrlTest>(
608 		"UrlTest::TestGettersSetters", &UrlTest::TestGettersSetters));
609 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestNullity",
610 		&UrlTest::TestNullity));
611 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestCopy",
612 		&UrlTest::TestCopy));
613 	suite.addTest(new CppUnit::TestCaller<UrlTest>(
614 		"UrlTest::ExplodeImplodeTest", &UrlTest::ExplodeImplodeTest));
615 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::PathOnly",
616 		&UrlTest::PathOnly));
617 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::RelativeUriTest",
618 		&UrlTest::RelativeUriTest));
619 	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::IDNTest",
620 		&UrlTest::IDNTest));
621 
622 	parent.addTest("UrlTest", &suite);
623 }
624