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
21UrlTest::UrlTest()
22{
23}
24
25
26UrlTest::~UrlTest()
27{
28}
29
30
31// Test that parsing a valid URL and converting back to string doesn't alter it
32void 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
64void 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
97void 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
126void 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
145void 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
159typedef 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
177const 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]:389/c=GB?objectClass?one",
197			{ "ldap",   "",        "", "[2001:db8::7]",389,"/c=GB",   "objectClass?one", "" } },
198		{ "ldap://[2001:db8::7]/c=GB?objectClass?one",
199			{ "ldap",   "",        "", "[2001:db8::7]",0,  "/c=GB",   "objectClass?one", "" } },
200		{ "HTTP://example.com.:80/%70a%74%68?a=%31#1%323",
201			{ "HTTP",   "",        "", "example.com.",80,  "/%70a%74%68","a=%31","1%323"} },
202		{ "/boot/home/Desktop/index.html",
203			{ "",   "",            "", "",             0,  "/boot/home/Desktop/index.html","",""} },
204		{ "//remote.host/boot/home/Desktop",
205			{ "",   "",            "", "remote.host",  0,  "/boot/home/Desktop","",""} },
206		{ "tag:haiku-os.org,2020:repositories/haiku/r1beta2/x86_gcc2",
207			{ "tag", "", "", "", 0, "haiku-os.org,2020:repositories/haiku/r1beta2/x86_gcc2" } }
208	};
209
210void UrlTest::ExplodeImplodeTest()
211{
212	uint8 testIndex;
213	BUrl testUrl;
214
215	for (testIndex = 0; testIndex < (sizeof(kTestExplode) / sizeof(ExplodeTest)); testIndex++)
216	{
217		NextSubTest();
218		testUrl.SetUrlString(kTestExplode[testIndex].url);
219
220		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].url),
221			BString(testUrl.UrlString()));
222		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.protocol),
223			BString(testUrl.Protocol()));
224		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.userName),
225			BString(testUrl.UserName()));
226		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.password),
227			BString(testUrl.Password()));
228		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.host),
229			BString(testUrl.Host()));
230		CPPUNIT_ASSERT_EQUAL(kTestExplode[testIndex].expected.port,
231			testUrl.Port());
232		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.path),
233			BString(testUrl.Path()));
234		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.request),
235			BString(testUrl.Request()));
236		CPPUNIT_ASSERT_EQUAL(BString(kTestExplode[testIndex].expected.fragment),
237			BString(testUrl.Fragment()));
238	}
239}
240
241
242void
243UrlTest::PathOnly()
244{
245	BUrl test = "lol";
246	CPPUNIT_ASSERT(test.HasPath());
247	CPPUNIT_ASSERT_EQUAL(BString("lol"), test.Path());
248	CPPUNIT_ASSERT_EQUAL(BString("lol"), test.UrlString());
249}
250
251
252void
253UrlTest::RelativeUriTest()
254{
255	// http://skew.org/uri/uri%5Ftests.html
256	struct RelativeUrl {
257		const char* base;
258		const char* relative;
259		const char* absolute;
260	};
261
262	const RelativeUrl tests[] = {
263		// The port must be preserved
264		{"http://host:81/",		"/path",				"http://host:81/path"},
265
266		// Tests from http://skew.org/uri/uri_tests.html
267		{"http://example.com/path?query#frag", "",
268			"http://example.com/path?query"},
269			// The fragment must be dropped when changing the path, but the
270			// query must be preserved
271		{"foo:a/b",				"../c",					"foo:/c"},
272			// foo:c would be more intuitive, and is what skew.org tests.
273			// However, foo:/c is what the RFC says we should get.
274		{"foo:a",				"foo:.",				"foo:"},
275		{"zz:abc",				"/foo/../../../bar",	"zz:/bar"},
276		{"zz:abc",				"/foo/../bar",			"zz:/bar"},
277		{"zz:abc",				"foo/../../../bar",		"zz:/bar"},
278			// zz:bar would be more intuitive, ...
279		{"zz:abc",				"zz:.",					"zz:"},
280		{"http://a/b/c/d;p?q",	"/.",					"http://a/"},
281		{"http://a/b/c/d;p?q",	"/.foo",				"http://a/.foo"},
282		{"http://a/b/c/d;p?q",	".foo",					"http://a/b/c/.foo"},
283		{"http://a/b/c/d;p?q",	"g:h",					"g:h"},
284
285		{"http://a/b/c/d;p?q",	"g:h",					"g:h"},
286		{"http://a/b/c/d;p?q",	"g",					"http://a/b/c/g"},
287		{"http://a/b/c/d;p?q",	"./g",					"http://a/b/c/g"},
288		{"http://a/b/c/d;p?q",	"g/",					"http://a/b/c/g/"},
289		{"http://a/b/c/d;p?q",	"/g",					"http://a/g"},
290		{"http://a/b/c/d;p?q",	"//g",					"http://g"},
291		{"http://a/b/c/d;p?q",	"?y",					"http://a/b/c/d;p?y"},
292		{"http://a/b/c/d;p?q",	"g?y",					"http://a/b/c/g?y"},
293		{"http://a/b/c/d;p?q",	"#s",					"http://a/b/c/d;p?q#s"},
294		{"http://a/b/c/d;p?q",	"g#s",					"http://a/b/c/g#s"},
295		{"http://a/b/c/d;p?q",	"g?y#s",				"http://a/b/c/g?y#s"},
296		{"http://a/b/c/d;p?q",	";x",					"http://a/b/c/;x"},
297		{"http://a/b/c/d;p?q",	"g;x",					"http://a/b/c/g;x"},
298		{"http://a/b/c/d;p?q",	"g;x?y#s",				"http://a/b/c/g;x?y#s"},
299		{"http://a/b/c/d;p?q",	"",						"http://a/b/c/d;p?q"},
300		{"http://a/b/c/d;p?q",	".",					"http://a/b/c/"},
301		{"http://a/b/c/d;p?q",	"./",					"http://a/b/c/"},
302		{"http://a/b/c/d;p?q",	"..",					"http://a/b/"},
303		{"http://a/b/c/d;p?q",	"../",					"http://a/b/"},
304		{"http://a/b/c/d;p?q",	"../g",					"http://a/b/g"},
305		{"http://a/b/c/d;p?q",	"../..",				"http://a/"},
306		{"http://a/b/c/d;p?q",	"../../",				"http://a/"},
307		{"http://a/b/c/d;p?q",	"../../g",				"http://a/g"},
308
309		// Parsers must be careful in handling cases where there are more
310		// relative path ".." segments than there are hierarchical levels in the
311		// base URI's path. Note that the ".." syntax cannot be used to change
312		// the authority component of a URI.
313		{"http://a/b/c/d;p?q",	"../../../g",			"http://a/g"},
314		{"http://a/b/c/d;p?q",	"../../../../g",		"http://a/g"},
315
316		// Similarly, parsers must remove the dot-segments "." and ".." when
317		// they are complete components of a path, but not when they are only
318		// part of a segment.
319		{"http://a/b/c/d;p?q",	"/./g",					"http://a/g"},
320		{"http://a/b/c/d;p?q",	"/../g",				"http://a/g"},
321		{"http://a/b/c/d;p?q",	"g.",					"http://a/b/c/g."},
322		{"http://a/b/c/d;p?q",	".g",					"http://a/b/c/.g"},
323		{"http://a/b/c/d;p?q",	"g..",					"http://a/b/c/g.."},
324		{"http://a/b/c/d;p?q",	"..g",					"http://a/b/c/..g"},
325
326		// Less likely are cases where the relative URI reference uses
327		// unnecessary or nonsensical forms of the "." and ".." complete path
328		// segments.
329		{"http://a/b/c/d;p?q",	"./../g",				"http://a/b/g"},
330		{"http://a/b/c/d;p?q",	"./g/.",				"http://a/b/c/g/"},
331		{"http://a/b/c/d;p?q",	"g/./h",				"http://a/b/c/g/h"},
332		{"http://a/b/c/d;p?q",	"g/../h",				"http://a/b/c/h"},
333		{"http://a/b/c/d;p?q",	"g;x=1/./y",			"http://a/b/c/g;x=1/y"},
334		{"http://a/b/c/d;p?q",	"g;x=1/../y",			"http://a/b/c/y"},
335
336		// Some applications fail to separate the reference's query and/or
337		// fragment components from a relative path before merging it with the
338		// base path and removing dot-segments. This error is rarely noticed,
339		// since typical usage of a fragment never includes the hierarchy ("/")
340		// character, and the query component is not normally used within
341		// relative references.
342		{"http://a/b/c/d;p?q",	"g?y/./x",			"http://a/b/c/g?y/./x"},
343		{"http://a/b/c/d;p?q",	"g?y/../x",			"http://a/b/c/g?y/../x"},
344		{"http://a/b/c/d;p?q",	"g#s/./x",			"http://a/b/c/g#s/./x"},
345		{"http://a/b/c/d;p?q",	"g#s/../x",			"http://a/b/c/g#s/../x"},
346
347		// Some parsers allow the scheme name to be present in a relative URI
348		// reference if it is the same as the base URI scheme. This is
349		// considered to be a loophole in prior specifications of partial URI
350		// [RFC1630]. Its use should be avoided, but is allowed for backward
351		// compatibility.
352		{"http://a/b/c/d;p?q",	"http:g",			"http:g"},
353		{"http://a/b/c/d;p?q",	"http:",			"http:"},
354
355		{"http://a/b/c/d;p?q",	"./g:h",			"http://a/b/c/g:h"},
356		{"http://a/b/c/d;p?q",	"/a/b/c/./../../g",	"http://a/a/g"},
357
358		{"http://a/b/c/d;p?q=1/2",	"g",			"http://a/b/c/g"},
359		{"http://a/b/c/d;p?q=1/2",	"./g",			"http://a/b/c/g"},
360		{"http://a/b/c/d;p?q=1/2",	"g/",			"http://a/b/c/g/"},
361		{"http://a/b/c/d;p?q=1/2",	"/g",			"http://a/g"},
362		{"http://a/b/c/d;p?q=1/2",	"//g",			"http://g"},
363		{"http://a/b/c/d;p?q=1/2",	"?y",			"http://a/b/c/d;p?y"},
364		{"http://a/b/c/d;p?q=1/2",	"g?y",			"http://a/b/c/g?y"},
365		{"http://a/b/c/d;p?q=1/2",	"g?y/./x",		"http://a/b/c/g?y/./x"},
366		{"http://a/b/c/d;p?q=1/2",	"g?y/../x",		"http://a/b/c/g?y/../x"},
367		{"http://a/b/c/d;p?q=1/2",	"g#s",			"http://a/b/c/g#s"},
368		{"http://a/b/c/d;p?q=1/2",	"g#s/./x",		"http://a/b/c/g#s/./x"},
369		{"http://a/b/c/d;p?q=1/2",	"g#s/../x",		"http://a/b/c/g#s/../x"},
370		{"http://a/b/c/d;p?q=1/2",	"./",			"http://a/b/c/"},
371		{"http://a/b/c/d;p?q=1/2",	"../",			"http://a/b/"},
372		{"http://a/b/c/d;p?q=1/2",	"../g",			"http://a/b/g"},
373		{"http://a/b/c/d;p?q=1/2",	"../../",		"http://a/"},
374		{"http://a/b/c/d;p?q=1/2",	"../../g",		"http://a/g"},
375
376		{"http://a/b/c/d;p=1/2?q",	"g",			"http://a/b/c/d;p=1/g"},
377		{"http://a/b/c/d;p=1/2?q",	"./g",			"http://a/b/c/d;p=1/g"},
378		{"http://a/b/c/d;p=1/2?q",	"g/",			"http://a/b/c/d;p=1/g/"},
379		{"http://a/b/c/d;p=1/2?q",	"g?y",			"http://a/b/c/d;p=1/g?y"},
380		{"http://a/b/c/d;p=1/2?q",	";x",			"http://a/b/c/d;p=1/;x"},
381		{"http://a/b/c/d;p=1/2?q",	"g;x",			"http://a/b/c/d;p=1/g;x"},
382		{"http://a/b/c/d;p=1/2?q", "g;x=1/./y", "http://a/b/c/d;p=1/g;x=1/y"},
383		{"http://a/b/c/d;p=1/2?q",	"g;x=1/../y",	"http://a/b/c/d;p=1/y"},
384		{"http://a/b/c/d;p=1/2?q",	"./",			"http://a/b/c/d;p=1/"},
385		{"http://a/b/c/d;p=1/2?q",	"../",			"http://a/b/c/"},
386		{"http://a/b/c/d;p=1/2?q",	"../g",			"http://a/b/c/g"},
387		{"http://a/b/c/d;p=1/2?q",	"../../",		"http://a/b/"},
388		{"http://a/b/c/d;p=1/2?q",	"../../g",		"http://a/b/g"},
389
390		// Empty host and directory
391		{"fred:///s//a/b/c",	"g:h",					"g:h"},
392		{"fred:///s//a/b/c",	"g",					"fred:///s//a/b/g"},
393		{"fred:///s//a/b/c",	"./g",					"fred:///s//a/b/g"},
394		{"fred:///s//a/b/c",	"g/",					"fred:///s//a/b/g/"},
395		{"fred:///s//a/b/c",	"/g",					"fred:///g"},
396		{"fred:///s//a/b/c",	"//g",					"fred://g"},
397		{"fred:///s//a/b/c",	"//g/x",				"fred://g/x"},
398		{"fred:///s//a/b/c",	"///g",					"fred:///g"},
399		{"fred:///s//a/b/c",	"./",					"fred:///s//a/b/"},
400		{"fred:///s//a/b/c",	"../",					"fred:///s//a/"},
401		{"fred:///s//a/b/c",	"../g",					"fred:///s//a/g"},
402		{"fred:///s//a/b/c",	"../..",				"fred:///s//"},
403		{"fred:///s//a/b/c",	"../../g",				"fred:///s//g"},
404		{"fred:///s//a/b/c",	"../../../g",			"fred:///s/g"},
405		{"fred:///s//a/b/c",	"../../../g",			"fred:///s/g"},
406
407		{"http:///s//a/b/c",	"g:h",					"g:h"},
408		{"http:///s//a/b/c",	"g",					"http:///s//a/b/g"},
409		{"http:///s//a/b/c",	"./g",					"http:///s//a/b/g"},
410		{"http:///s//a/b/c",	"g/",					"http:///s//a/b/g/"},
411		{"http:///s//a/b/c",	"/g",					"http:///g"},
412		{"http:///s//a/b/c",	"//g",					"http://g"},
413		{"http:///s//a/b/c",	"//g/x",				"http://g/x"},
414		{"http:///s//a/b/c",	"///g",					"http:///g"},
415		{"http:///s//a/b/c",	"./",					"http:///s//a/b/"},
416		{"http:///s//a/b/c",	"../",					"http:///s//a/"},
417		{"http:///s//a/b/c",	"../g",					"http:///s//a/g"},
418		{"http:///s//a/b/c",	"../..",				"http:///s//"},
419		{"http:///s//a/b/c",	"../../g",				"http:///s//g"},
420		{"http:///s//a/b/c",	"../../../g",			"http:///s/g"},
421		{"http:///s//a/b/c",	"../../../g",			"http:///s/g"},
422
423		{"foo:xyz",				"bar:abc",				"bar:abc"},
424		{"http://example/x/y/z","../abc",				"http://example/x/abc"},
425		{"http://example2/x/y/z","http://example/x/abc","http://example/x/abc"},
426		{"http://ex/x/y/z",		"../r",					"http://ex/x/r"},
427		{"http://ex/x/y",		"q/r",					"http://ex/x/q/r"},
428		{"http://ex/x/y",		"q/r#s",				"http://ex/x/q/r#s"},
429		{"http://ex/x/y",		"q/r#s/t",				"http://ex/x/q/r#s/t"},
430		{"http://ex/x/y",		"ftp://ex/x/q/r",		"ftp://ex/x/q/r"},
431		{"http://ex/x/y",		"",						"http://ex/x/y"},
432		{"http://ex/x/y/",		"",						"http://ex/x/y/"},
433		{"http://ex/x/y/pdq",	"",						"http://ex/x/y/pdq"},
434		{"http://ex/x/y/",		"z/",					"http://ex/x/y/z/"},
435
436		{"file:/swap/test/animal.rdf", "#Animal",
437			"file:/swap/test/animal.rdf#Animal"},
438		{"file:/e/x/y/z",		"../abc",				"file:/e/x/abc"},
439		{"file:/example2/x/y/z","/example/x/abc",		"file:/example/x/abc"},
440		{"file:/e/x/y/z",		"../r",					"file:/e/x/r"},
441		{"file:/e/x/y/z",		"/r",					"file:/r"},
442		{"file:/e/x/y",			"q/r",					"file:/e/x/q/r"},
443		{"file:/e/x/y",			"q/r#s",				"file:/e/x/q/r#s"},
444		{"file:/e/x/y",			"q/r#",					"file:/e/x/q/r#"},
445		{"file:/e/x/y",			"q/r#s/t",				"file:/e/x/q/r#s/t"},
446		{"file:/e/x/y",			"ftp://ex/x/q/r",		"ftp://ex/x/q/r"},
447		{"file:/e/x/y",			"",						"file:/e/x/y"},
448		{"file:/e/x/y/",		"",						"file:/e/x/y/"},
449		{"file:/e/x/y/pdq",		"",						"file:/e/x/y/pdq"},
450		{"file:/e/x/y/",		"z/",					"file:/e/x/y/z/"},
451		{"file:/devel/WWW/2000/10/swap/test/reluri-1.n3",
452			"file://meetings.example.com/cal#m1",
453			"file://meetings.example.com/cal#m1"},
454		{"file:/home/connolly/w3ccvs/WWW/2000/10/swap/test/reluri-1.n3",
455			"file://meetings.example.com/cal#m1",
456			"file://meetings.example.com/cal#m1"},
457		{"file:/some/dir/foo",		"./#blort",		"file:/some/dir/#blort"},
458		{"file:/some/dir/foo",		"./#",			"file:/some/dir/#"},
459
460		{"http://example/x/abc.efg", "./",			"http://example/x/"},
461		{"http://example2/x/y/z", "//example/x/abc","http://example/x/abc"},
462		{"http://ex/x/y/z",			"/r",			"http://ex/r"},
463		{"http://ex/x/y",			"./q:r",		"http://ex/x/q:r"},
464		{"http://ex/x/y",			"./p=q:r",		"http://ex/x/p=q:r"},
465		{"http://ex/x/y?pp/qq",		"?pp/rr",		"http://ex/x/y?pp/rr"},
466		{"http://ex/x/y?pp/qq",		"y/z",			"http://ex/x/y/z"},
467
468		{"mailto:local", "local/qual@domain.org#frag",
469			"mailto:local/qual@domain.org#frag"},
470		{"mailto:local/qual1@domain1.org", "more/qual2@domain2.org#frag",
471			"mailto:local/more/qual2@domain2.org#frag"},
472
473		{"http://ex/x/y?q",		"y?q",			"http://ex/x/y?q"},
474		{"http://ex?p",			"x/y?q",		"http://ex/x/y?q"},
475		{"foo:a/b",				"c/d",			"foo:a/c/d"},
476		{"foo:a/b",				"/c/d",			"foo:/c/d"},
477		{"foo:a/b?c#d",			"",				"foo:a/b?c"},
478		{"foo:a",				"b/c",			"foo:b/c"},
479		{"foo:/a/y/z",			"../b/c",		"foo:/a/b/c"},
480		{"foo:a",				"./b/c",		"foo:b/c"},
481		{"foo:a",				"/./b/c",		"foo:/b/c"},
482		{"foo://a//b/c",		"../../d",		"foo://a/d"},
483		{"foo:a",				".",			"foo:"},
484		{"foo:a",				"..",			"foo:"},
485
486		{"http://example/x/y%2Fz", "abc",			"http://example/x/abc"},
487		{"http://example/a/x/y/z", "../../x%2Fabc", "http://example/a/x%2Fabc"},
488		{"http://example/a/x/y%2Fz", "../x%2Fabc", "http://example/a/x%2Fabc"},
489		{"http://example/x%2Fy/z", "abc", "http://example/x%2Fy/abc"},
490		{"http://ex/x/y", "q%3Ar", "http://ex/x/q%3Ar"},
491		{"http://example/x/y%2Fz", "/x%2Fabc", "http://example/x%2Fabc"},
492		{"http://example/x/y/z", "/x%2Fabc", "http://example/x%2Fabc"},
493
494		{"mailto:local1@domain1?query1", "local2@domain2",
495			"mailto:local2@domain2"},
496		{"mailto:local1@domain1", "local2@domain2?query2",
497			"mailto:local2@domain2?query2"},
498		{"mailto:local1@domain1?query1", "local2@domain2?query2",
499			"mailto:local2@domain2?query2"},
500		{"mailto:local@domain?query1", "?query2",
501			"mailto:local@domain?query2"},
502		{"mailto:?query1", "local2@domain2?query2",
503			"mailto:local2@domain2?query2"},
504		{"mailto:local1@domain1?query1", "?query2",
505			"mailto:local1@domain1?query2"},
506
507		{"foo:bar", "http://example/a/b?c/../d", "http://example/a/b?c/../d"},
508		{"foo:bar", "http://example/a/b#c/../d", "http://example/a/b#c/../d"},
509		{"http://example.org/base/uri", "http:this", "http:this"},
510		{"http:base", "http:this", "http:this"},
511		{"f:/a", ".//g", "f://g"},
512		{"f://example.org/base/a", "b/c//d/e", "f://example.org/base/b/c//d/e"},
513		{"mid:m@example.ord/c@example.org", "m2@example.ord/c2@example.org",
514			"mid:m@example.ord/m2@example.ord/c2@example.org"},
515		{"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/", "mini1.xml",
516			"file:///C:/DEV/Haskell/lib/HXmlToolbox-3.01/examples/mini1.xml"},
517		{"foo:a/y/z", "../b/c", "foo:a/b/c"},
518		{"foo:", "b", "foo:b"},
519		{"foo://a", "b", "foo://a/b"},
520		{"foo://a?q", "b", "foo://a/b"},
521		{"foo://a", "b?q", "foo://a/b?q"},
522		{"foo://a?r", "b?q", "foo://a/b?q"},
523	};
524
525	BString message(" Base: ");
526	for (unsigned int index = 0; index < sizeof(tests) / sizeof(RelativeUrl);
527		index++)
528	{
529		NextSubTest();
530
531		BUrl baseUrl(tests[index].base);
532
533		message.Truncate(7, true);
534		message << tests[index].base;
535		message << " Relative: ";
536		message << tests[index].relative;
537
538		CPPUNIT_ASSERT_EQUAL_MESSAGE(message.String(),
539			BString(tests[index].absolute),
540			BUrl(baseUrl, tests[index].relative).UrlString());
541	}
542}
543
544
545void
546UrlTest::IDNTest()
547{
548	// http://www.w3.org/2004/04/uri-rel-test.html
549	// TODO We need to decide wether to store them as UTF-8 or IDNA/punycode.
550
551	struct Test {
552		const char* escaped;
553		const char* decoded;
554	};
555
556	Test tests[] = {
557		{ "http://www.w%33.org", "http://www.w3.org" },
558		{ "http://r%C3%A4ksm%C3%B6rg%C3%A5s.josefsson.org",
559			"http://xn--rksmrgs-5wao1o.josefsson.org" },
560		{ "http://%E7%B4%8D%E8%B1%86.w3.mag.keio.ac.jp",
561			"http://xn--99zt52a.w3.mag.keio.ac.jp" },
562		{ "http://www.%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA"
563			"%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3"
564			"%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82"
565			"%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0"
566			"%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3"
567			"%81%9F%E3%82%8A%E3%81%AA%E3%81%84.w3.mag.keio.ac.jp/",
568			"http://www.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9b"
569			"ya3kc6lra.w3.mag.keio.ac.jp/" },
570
571		{ "http://%E3%81%BB%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA%E3"
572			"%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3%82"
573			"%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82%81"
574			"%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0%E3"
575			"%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81"
576			"%9F%E3%82%8A%E3%81%AA%E3%81%84.%E3%81%BB%E3%82%93%E3%81%A8%E3%81"
577			"%86%E3%81%AB%E3%81%AA%E3%81%8C%E3%81%84%E3%82%8F%E3%81%91%E3%81%AE"
578			"%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81%84%E3%81%A9%E3%82%81%E3"
579			"%81%84%E3%82%93%E3%82%81%E3%81%84%E3%81%AE%E3%82%89%E3%81%B9%E3%82"
580			"%8B%E3%81%BE%E3%81%A0%E3%81%AA%E3%81%8C%E3%81%8F%E3%81%97%E3%81%AA"
581			"%E3%81%84%E3%81%A8%E3%81%9F%E3%82%8A%E3%81%AA%E3%81%84.%E3%81%BB"
582			"%E3%82%93%E3%81%A8%E3%81%86%E3%81%AB%E3%81%AA%E3%81%8C%E3%81%84%E3"
583			"%82%8F%E3%81%91%E3%81%AE%E3%82%8F%E3%81%8B%E3%82%89%E3%81%AA%E3%81"
584			"%84%E3%81%A9%E3%82%81%E3%81%84%E3%82%93%E3%82%81%E3%81%84%E3%81%AE"
585			"%E3%82%89%E3%81%B9%E3%82%8B%E3%81%BE%E3%81%A0%E3%81%AA%E3%81%8C%E3"
586			"%81%8F%E3%81%97%E3%81%AA%E3%81%84%E3%81%A8%E3%81%9F%E3%82%8A%E3%81"
587			"%AA%E3%81%84.w3.mag.keio.ac.jp/",
588			"http://xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3k"
589			"c6lra.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3kc6"
590			"lra.xn--n8jaaaaai5bhf7as8fsfk3jnknefdde3fg11amb5gzdb4wi9bya3kc6lr"
591			"a.w3.mag.keio.ac.jp/" },
592		{ NULL, NULL }
593	};
594
595	for (int i = 0; tests[i].escaped != NULL; i++)
596	{
597		NextSubTest();
598
599		BUrl url(tests[i].escaped);
600		url.UrlDecode();
601
602		BUrl idn(tests[i].decoded);
603		status_t success = idn.IDNAToUnicode();
604
605		CPPUNIT_ASSERT_EQUAL(B_OK, success);
606		CPPUNIT_ASSERT_EQUAL(url.UrlString(), idn.UrlString());
607	}
608}
609
610
611/* static */ void
612UrlTest::AddTests(BTestSuite& parent)
613{
614	CppUnit::TestSuite& suite = *new CppUnit::TestSuite("UrlTest");
615
616	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::ParseTest",
617		&UrlTest::ParseTest));
618	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestIsValid",
619		&UrlTest::TestIsValid));
620	suite.addTest(new CppUnit::TestCaller<UrlTest>(
621		"UrlTest::TestGettersSetters", &UrlTest::TestGettersSetters));
622	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestNullity",
623		&UrlTest::TestNullity));
624	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::TestCopy",
625		&UrlTest::TestCopy));
626	suite.addTest(new CppUnit::TestCaller<UrlTest>(
627		"UrlTest::ExplodeImplodeTest", &UrlTest::ExplodeImplodeTest));
628	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::PathOnly",
629		&UrlTest::PathOnly));
630	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::RelativeUriTest",
631		&UrlTest::RelativeUriTest));
632	suite.addTest(new CppUnit::TestCaller<UrlTest>("UrlTest::IDNTest",
633		&UrlTest::IDNTest));
634
635	parent.addTest("UrlTest", &suite);
636}
637