1# Copyright (c) 2004-2007 Divmod.
2# See LICENSE for details.
3
4"""
5Tests for L{nevow.url}.
6"""
7
8import urlparse, urllib
9
10from nevow import context, url, inevow, util, loaders
11from nevow import tags
12from nevow.testutil import TestCase, FakeRequest
13from nevow.flat import flatten
14
15theurl = "http://www.foo.com:80/a/nice/path/?zot=23&zut"
16
17# RFC1808 relative tests. Not all of these pass yet.
18rfc1808_relative_link_base='http://a/b/c/d;p?q#f'
19rfc1808_relative_link_tests = [
20    # "Normal"
21    ('g:h', 'g:h'),
22    ('g', 'http://a/b/c/g'),
23    ('./g', 'http://a/b/c/g'),
24    ('g/', 'http://a/b/c/g/'),
25    ('/g', 'http://a/g'),
26    ('//g', 'http://g'),
27    ('?y', 'http://a/b/c/d;p?y'),
28    ('g?y', 'http://a/b/c/g?y'),
29    ('g?y/./x', 'http://a/b/c/g?y/./x'),
30    ('#s', 'http://a/b/c/d;p?q#s'),
31    ('g#s', 'http://a/b/c/g#s'),
32    ('g#s/./x', 'http://a/b/c/g#s/./x'),
33    ('g?y#s', 'http://a/b/c/g?y#s'),
34    #(';x', 'http://a/b/c/d;x'),
35    ('g;x', 'http://a/b/c/g;x'),
36    ('g;x?y#s', 'http://a/b/c/g;x?y#s'),
37    ('.', 'http://a/b/c/'),
38    ('./', 'http://a/b/c/'),
39    ('..', 'http://a/b/'),
40    ('../', 'http://a/b/'),
41    ('../g', 'http://a/b/g'),
42    #('../..', 'http://a/'),
43    #('../../', 'http://a/'),
44    ('../../g', 'http://a/g'),
45
46    # "Abnormal"
47    ('', 'http://a/b/c/d;p?q#f'),
48    #('../../../g', 'http://a/../g'),
49    #('../../../../g', 'http://a/../../g'),
50    #('/./g', 'http://a/./g'),
51    #('/../g', 'http://a/../g'),
52    ('g.', 'http://a/b/c/g.'),
53    ('.g', 'http://a/b/c/.g'),
54    ('g..', 'http://a/b/c/g..'),
55    ('..g', 'http://a/b/c/..g'),
56    ('./../g', 'http://a/b/g'),
57    ('./g/.', 'http://a/b/c/g/'),
58    ('g/./h', 'http://a/b/c/g/h'),
59    ('g/../h', 'http://a/b/c/h'),
60    #('http:g', 'http:g'),          # Not sure whether the spec means
61    #('http:', 'http:'),            # these two are valid tests or not.
62    ]
63
64
65
66class _IncompatibleSignatureURL(url.URL):
67    """
68    A test fixture for verifying that subclasses which override C{cloneURL}
69    won't be copied by any other means (e.g. constructing C{self.__class___}
70    directly).  It accomplishes this by having a constructor signature which
71    is incompatible with L{url.URL}'s.
72    """
73    def __init__(
74        self, magicValue, scheme, netloc, pathsegs, querysegs, fragment):
75        url.URL.__init__(self, scheme, netloc, pathsegs, querysegs, fragment)
76        self.magicValue = magicValue
77
78
79    def cloneURL(self, scheme, netloc, pathsegs, querysegs, fragment):
80        """
81        Override the base implementation to pass along C{self.magicValue}.
82        """
83        return self.__class__(
84            self.magicValue, scheme, netloc, pathsegs, querysegs, fragment)
85
86
87
88class TestURL(TestCase):
89    def test_fromString(self):
90        urlpath = url.URL.fromString(theurl)
91        self.assertEquals(theurl, str(urlpath))
92
93    def test_roundtrip(self):
94        tests = (
95            "http://localhost",
96            "http://localhost/",
97            "http://localhost/foo",
98            "http://localhost/foo/",
99            "http://localhost/foo!!bar/",
100            "http://localhost/foo%20bar/",
101            "http://localhost/foo%2Fbar/",
102            "http://localhost/foo?n",
103            "http://localhost/foo?n=v",
104            "http://localhost/foo?n=%2Fa%2Fb",
105            "http://example.com/foo!%40%24bar?b!%40z=123",
106            "http://localhost/asd?a=asd%20sdf%2F345",
107            "http://localhost/#%7F",
108            )
109        for test in tests:
110            result = str(url.URL.fromString(test))
111            self.assertEquals(test, result)
112
113    def test_fromRequest(self):
114        request = FakeRequest(uri='/a/nice/path/?zot=23&zut',
115                              currentSegments=["a", "nice", "path", ""],
116                              headers={'host': 'www.foo.com:80'})
117        urlpath = url.URL.fromRequest(request)
118        self.assertEquals(theurl, str(urlpath))
119
120    def test_fromContext(self):
121
122        r = FakeRequest(uri='/a/b/c')
123        urlpath = url.URL.fromContext(context.RequestContext(tag=r))
124        self.assertEquals('http://localhost/', str(urlpath))
125
126        r.prepath = ['a']
127        urlpath = url.URL.fromContext(context.RequestContext(tag=r))
128        self.assertEquals('http://localhost/a', str(urlpath))
129
130        r = FakeRequest(uri='/a/b/c?foo=bar')
131        r.prepath = ['a','b']
132        urlpath = url.URL.fromContext(context.RequestContext(tag=r))
133        self.assertEquals('http://localhost/a/b?foo=bar', str(urlpath))
134
135    def test_equality(self):
136        urlpath = url.URL.fromString(theurl)
137        self.failUnlessEqual(urlpath, url.URL.fromString(theurl))
138        self.failIfEqual(urlpath, url.URL.fromString('ftp://www.anotherinvaliddomain.com/foo/bar/baz/?zot=21&zut'))
139
140
141    def test_fragmentEquality(self):
142        """
143        An URL created with the empty string for a fragment compares equal
144        to an URL created with C{None} for a fragment.
145        """
146        self.assertEqual(url.URL(fragment=''), url.URL(fragment=None))
147
148
149    def test_parent(self):
150        urlpath = url.URL.fromString(theurl)
151        self.assertEquals("http://www.foo.com:80/a/nice/?zot=23&zut",
152                          str(urlpath.parent()))
153
154
155    def test_path(self):
156        """
157        L{URL.path} should be a C{str} giving the I{path} portion of the URL
158        only.  Certain bytes should not be quoted.
159        """
160        urlpath = url.URL.fromString("http://example.com/foo/bar?baz=quux#foobar")
161        self.assertEqual(urlpath.path, "foo/bar")
162        urlpath = url.URL.fromString("http://example.com/foo%2Fbar?baz=quux#foobar")
163        self.assertEqual(urlpath.path, "foo%2Fbar")
164        urlpath = url.URL.fromString("http://example.com/-_.!*'()?baz=quux#foo")
165        self.assertEqual(urlpath.path, "-_.!*'()")
166
167
168    def test_parentdir(self):
169        urlpath = url.URL.fromString(theurl)
170        self.assertEquals("http://www.foo.com:80/a/nice/?zot=23&zut",
171                          str(urlpath.parentdir()))
172        urlpath = url.URL.fromString('http://www.foo.com/a')
173        self.assertEquals("http://www.foo.com/",
174                          str(urlpath.parentdir()))
175        urlpath = url.URL.fromString('http://www.foo.com/a/')
176        self.assertEquals("http://www.foo.com/",
177                          str(urlpath.parentdir()))
178        urlpath = url.URL.fromString('http://www.foo.com/a/b')
179        self.assertEquals("http://www.foo.com/",
180                          str(urlpath.parentdir()))
181        urlpath = url.URL.fromString('http://www.foo.com/a/b/')
182        self.assertEquals("http://www.foo.com/a/",
183                          str(urlpath.parentdir()))
184        urlpath = url.URL.fromString('http://www.foo.com/a/b/c')
185        self.assertEquals("http://www.foo.com/a/",
186                          str(urlpath.parentdir()))
187        urlpath = url.URL.fromString('http://www.foo.com/a/b/c/')
188        self.assertEquals("http://www.foo.com/a/b/",
189                          str(urlpath.parentdir()))
190        urlpath = url.URL.fromString('http://www.foo.com/a/b/c/d')
191        self.assertEquals("http://www.foo.com/a/b/",
192                          str(urlpath.parentdir()))
193        urlpath = url.URL.fromString('http://www.foo.com/a/b/c/d/')
194        self.assertEquals("http://www.foo.com/a/b/c/",
195                          str(urlpath.parentdir()))
196
197    def test_parent_root(self):
198        urlpath = url.URL.fromString('http://www.foo.com/')
199        self.assertEquals("http://www.foo.com/",
200                          str(urlpath.parentdir()))
201        self.assertEquals("http://www.foo.com/",
202                          str(urlpath.parentdir().parentdir()))
203
204    def test_child(self):
205        urlpath = url.URL.fromString(theurl)
206        self.assertEquals("http://www.foo.com:80/a/nice/path/gong?zot=23&zut",
207                          str(urlpath.child('gong')))
208        self.assertEquals("http://www.foo.com:80/a/nice/path/gong%2F?zot=23&zut",
209                          str(urlpath.child('gong/')))
210        self.assertEquals(
211            "http://www.foo.com:80/a/nice/path/gong%2Fdouble?zot=23&zut",
212            str(urlpath.child('gong/double')))
213        self.assertEquals(
214            "http://www.foo.com:80/a/nice/path/gong%2Fdouble%2F?zot=23&zut",
215            str(urlpath.child('gong/double/')))
216
217    def test_child_init_tuple(self):
218        self.assertEquals(
219            "http://www.foo.com/a/b/c",
220            str(url.URL(netloc="www.foo.com",
221                        pathsegs=['a', 'b']).child("c")))
222
223    def test_child_init_root(self):
224        self.assertEquals(
225            "http://www.foo.com/c",
226            str(url.URL(netloc="www.foo.com").child("c")))
227
228    def test_sibling(self):
229        urlpath = url.URL.fromString(theurl)
230        self.assertEquals(
231            "http://www.foo.com:80/a/nice/path/sister?zot=23&zut",
232            str(urlpath.sibling('sister')))
233        # use an url without trailing '/' to check child removal
234        theurl2 = "http://www.foo.com:80/a/nice/path?zot=23&zut"
235        urlpath = url.URL.fromString(theurl2)
236        self.assertEquals(
237            "http://www.foo.com:80/a/nice/sister?zot=23&zut",
238            str(urlpath.sibling('sister')))
239
240    def test_curdir(self):
241        urlpath = url.URL.fromString(theurl)
242        self.assertEquals(theurl, str(urlpath))
243        # use an url without trailing '/' to check object removal
244        theurl2 = "http://www.foo.com:80/a/nice/path?zot=23&zut"
245        urlpath = url.URL.fromString(theurl2)
246        self.assertEquals("http://www.foo.com:80/a/nice/?zot=23&zut",
247                          str(urlpath.curdir()))
248
249    def test_click(self):
250        urlpath = url.URL.fromString(theurl)
251        # a null uri should be valid (return here)
252        self.assertEquals("http://www.foo.com:80/a/nice/path/?zot=23&zut",
253                          str(urlpath.click("")))
254        # a simple relative path remove the query
255        self.assertEquals("http://www.foo.com:80/a/nice/path/click",
256                          str(urlpath.click("click")))
257        # an absolute path replace path and query
258        self.assertEquals("http://www.foo.com:80/click",
259                          str(urlpath.click("/click")))
260        # replace just the query
261        self.assertEquals("http://www.foo.com:80/a/nice/path/?burp",
262                          str(urlpath.click("?burp")))
263        # one full url to another should not generate '//' between netloc and pathsegs
264        self.failIfIn("//foobar", str(urlpath.click('http://www.foo.com:80/foobar')))
265
266        # from a url with no query clicking a url with a query,
267        # the query should be handled properly
268        u = url.URL.fromString('http://www.foo.com:80/me/noquery')
269        self.failUnlessEqual('http://www.foo.com:80/me/17?spam=158',
270                             str(u.click('/me/17?spam=158')))
271
272        # Check that everything from the path onward is removed when the click link
273        # has no path.
274        u = url.URL.fromString('http://localhost/foo?abc=def')
275        self.failUnlessEqual(str(u.click('http://www.python.org')), 'http://www.python.org/')
276
277
278    def test_cloneUnchanged(self):
279        """
280        Verify that L{url.URL.cloneURL} doesn't change any of the arguments it
281        is passed.
282        """
283        urlpath = url.URL.fromString('https://x:1/y?z=1#A')
284        self.assertEqual(
285            urlpath.cloneURL(urlpath.scheme,
286                             urlpath.netloc,
287                             urlpath._qpathlist,
288                             urlpath._querylist,
289                             urlpath.fragment),
290            urlpath)
291
292
293    def _makeIncompatibleSignatureURL(self, magicValue):
294        return _IncompatibleSignatureURL(magicValue, '', '', None, None, '')
295
296
297    def test_clickCloning(self):
298        """
299        Verify that L{url.URL.click} uses L{url.URL.cloneURL} to construct its
300        return value.
301        """
302        urlpath = self._makeIncompatibleSignatureURL(8789)
303        self.assertEqual(urlpath.click('/').magicValue, 8789)
304
305
306    def test_clickCloningScheme(self):
307        """
308        Verify that L{url.URL.click} uses L{url.URL.cloneURL} to construct its
309        return value, when the clicked url has a scheme.
310        """
311        urlpath = self._makeIncompatibleSignatureURL(8031)
312        self.assertEqual(urlpath.click('https://foo').magicValue, 8031)
313
314
315    def test_addCloning(self):
316        """
317        Verify that L{url.URL.add} uses L{url.URL.cloneURL} to construct its
318        return value.
319        """
320        urlpath = self._makeIncompatibleSignatureURL(8789)
321        self.assertEqual(urlpath.add('x').magicValue, 8789)
322
323
324    def test_replaceCloning(self):
325        """
326        Verify that L{url.URL.replace} uses L{url.URL.cloneURL} to construct
327        its return value.
328        """
329        urlpath = self._makeIncompatibleSignatureURL(8789)
330        self.assertEqual(urlpath.replace('x').magicValue, 8789)
331
332
333    def test_removeCloning(self):
334        """
335        Verify that L{url.URL.remove} uses L{url.URL.cloneURL} to construct
336        its return value.
337        """
338        urlpath = self._makeIncompatibleSignatureURL(8789)
339        self.assertEqual(urlpath.remove('x').magicValue, 8789)
340
341
342    def test_clearCloning(self):
343        """
344        Verify that L{url.URL.clear} uses L{url.URL.cloneURL} to construct its
345        return value.
346        """
347        urlpath = self._makeIncompatibleSignatureURL(8789)
348        self.assertEqual(urlpath.clear().magicValue, 8789)
349
350
351    def test_anchorCloning(self):
352        """
353        Verify that L{url.URL.anchor} uses L{url.URL.cloneURL} to construct
354        its return value.
355        """
356        urlpath = self._makeIncompatibleSignatureURL(8789)
357        self.assertEqual(urlpath.anchor().magicValue, 8789)
358
359
360    def test_secureCloning(self):
361        """
362        Verify that L{url.URL.secure} uses L{url.URL.cloneURL} to construct its
363        return value.
364        """
365        urlpath = self._makeIncompatibleSignatureURL(8789)
366        self.assertEqual(urlpath.secure().magicValue, 8789)
367
368
369    def test_clickCollapse(self):
370        tests = [
371            ['http://localhost/', '.', 'http://localhost/'],
372            ['http://localhost/', '..', 'http://localhost/'],
373            ['http://localhost/a/b/c', '.', 'http://localhost/a/b/'],
374            ['http://localhost/a/b/c', '..', 'http://localhost/a/'],
375            ['http://localhost/a/b/c', './d/e', 'http://localhost/a/b/d/e'],
376            ['http://localhost/a/b/c', '../d/e', 'http://localhost/a/d/e'],
377            ['http://localhost/a/b/c', '/./d/e', 'http://localhost/d/e'],
378            ['http://localhost/a/b/c', '/../d/e', 'http://localhost/d/e'],
379            ['http://localhost/a/b/c/', '../../d/e/', 'http://localhost/a/d/e/'],
380            ['http://localhost/a/./c', '../d/e', 'http://localhost/d/e'],
381            ['http://localhost/a/./c/', '../d/e', 'http://localhost/a/d/e'],
382            ['http://localhost/a/b/c/d', './e/../f/../g', 'http://localhost/a/b/c/g'],
383            ['http://localhost/a/b/c', 'd//e', 'http://localhost/a/b/d//e'],
384            ]
385        for start, click, result in tests:
386            self.assertEquals(
387                str(url.URL.fromString(start).click(click)),
388                result
389                )
390
391    def test_add(self):
392        urlpath = url.URL.fromString(theurl)
393        self.assertEquals(
394            "http://www.foo.com:80/a/nice/path/?zot=23&zut&burp",
395            str(urlpath.add("burp")))
396        self.assertEquals(
397            "http://www.foo.com:80/a/nice/path/?zot=23&zut&burp=xxx",
398            str(urlpath.add("burp", "xxx")))
399        self.assertEquals(
400            "http://www.foo.com:80/a/nice/path/?zot=23&zut&burp=xxx&zing",
401            str(urlpath.add("burp", "xxx").add("zing")))
402        # note the inversion!
403        self.assertEquals(
404            "http://www.foo.com:80/a/nice/path/?zot=23&zut&zing&burp=xxx",
405            str(urlpath.add("zing").add("burp", "xxx")))
406        # note the two values for the same name
407        self.assertEquals(
408            "http://www.foo.com:80/a/nice/path/?zot=23&zut&burp=xxx&zot=32",
409            str(urlpath.add("burp", "xxx").add("zot", 32)))
410
411    def test_add_noquery(self):
412        # fromString is a different code path, test them both
413        self.assertEquals(
414            "http://www.foo.com:80/a/nice/path/?foo=bar",
415            str(url.URL.fromString("http://www.foo.com:80/a/nice/path/")
416                .add("foo", "bar")))
417        self.assertEquals(
418            "http://www.foo.com/?foo=bar",
419            str(url.URL(netloc="www.foo.com").add("foo", "bar")))
420
421    def test_replace(self):
422        urlpath = url.URL.fromString(theurl)
423        self.assertEquals(
424            "http://www.foo.com:80/a/nice/path/?zot=32&zut",
425            str(urlpath.replace("zot", 32)))
426        # replace name without value with name/value and vice-versa
427        self.assertEquals(
428            "http://www.foo.com:80/a/nice/path/?zot&zut=itworked",
429            str(urlpath.replace("zot").replace("zut", "itworked")))
430        # Q: what happens when the query has two values and we replace?
431        # A: we replace both values with a single one
432        self.assertEquals(
433            "http://www.foo.com:80/a/nice/path/?zot=32&zut",
434            str(urlpath.add("zot", "xxx").replace("zot", 32)))
435
436    def test_fragment(self):
437        urlpath = url.URL.fromString(theurl)
438        self.assertEquals(
439            "http://www.foo.com:80/a/nice/path/?zot=23&zut#hiboy",
440            str(urlpath.anchor("hiboy")))
441        self.assertEquals(
442            "http://www.foo.com:80/a/nice/path/?zot=23&zut",
443            str(urlpath.anchor()))
444        self.assertEquals(
445            "http://www.foo.com:80/a/nice/path/?zot=23&zut",
446            str(urlpath.anchor('')))
447
448    def test_clear(self):
449        urlpath = url.URL.fromString(theurl)
450        self.assertEquals(
451            "http://www.foo.com:80/a/nice/path/?zut",
452            str(urlpath.clear("zot")))
453        self.assertEquals(
454            "http://www.foo.com:80/a/nice/path/?zot=23",
455            str(urlpath.clear("zut")))
456        # something stranger, query with two values, both should get cleared
457        self.assertEquals(
458            "http://www.foo.com:80/a/nice/path/?zut",
459            str(urlpath.add("zot", 1971).clear("zot")))
460        # two ways to clear the whole query
461        self.assertEquals(
462            "http://www.foo.com:80/a/nice/path/",
463            str(urlpath.clear("zut").clear("zot")))
464        self.assertEquals(
465            "http://www.foo.com:80/a/nice/path/",
466            str(urlpath.clear()))
467
468    def test_secure(self):
469        self.assertEquals(str(url.URL.fromString('http://localhost/').secure()), 'https://localhost/')
470        self.assertEquals(str(url.URL.fromString('http://localhost/').secure(True)), 'https://localhost/')
471        self.assertEquals(str(url.URL.fromString('https://localhost/').secure()), 'https://localhost/')
472        self.assertEquals(str(url.URL.fromString('https://localhost/').secure(False)), 'http://localhost/')
473        self.assertEquals(str(url.URL.fromString('http://localhost/').secure(False)), 'http://localhost/')
474        self.assertEquals(str(url.URL.fromString('http://localhost/foo').secure()), 'https://localhost/foo')
475        self.assertEquals(str(url.URL.fromString('http://localhost/foo?bar=1').secure()), 'https://localhost/foo?bar=1')
476        self.assertEquals(str(url.URL.fromString('http://localhost/').secure(port=443)), 'https://localhost/')
477        self.assertEquals(str(url.URL.fromString('http://localhost:8080/').secure(port=8443)), 'https://localhost:8443/')
478        self.assertEquals(str(url.URL.fromString('https://localhost:8443/').secure(False, 8080)), 'http://localhost:8080/')
479
480
481    def test_eq_same(self):
482        u = url.URL.fromString('http://localhost/')
483        self.failUnless(u == u, "%r != itself" % u)
484
485    def test_eq_similar(self):
486        u1 = url.URL.fromString('http://localhost/')
487        u2 = url.URL.fromString('http://localhost/')
488        self.failUnless(u1 == u2, "%r != %r" % (u1, u2))
489
490    def test_eq_different(self):
491        u1 = url.URL.fromString('http://localhost/a')
492        u2 = url.URL.fromString('http://localhost/b')
493        self.failIf(u1 == u2, "%r != %r" % (u1, u2))
494
495    def test_eq_apples_vs_oranges(self):
496        u = url.URL.fromString('http://localhost/')
497        self.failIf(u == 42, "URL must not equal a number.")
498        self.failIf(u == object(), "URL must not equal an object.")
499
500    def test_ne_same(self):
501        u = url.URL.fromString('http://localhost/')
502        self.failIf(u != u, "%r == itself" % u)
503
504    def test_ne_similar(self):
505        u1 = url.URL.fromString('http://localhost/')
506        u2 = url.URL.fromString('http://localhost/')
507        self.failIf(u1 != u2, "%r == %r" % (u1, u2))
508
509    def test_ne_different(self):
510        u1 = url.URL.fromString('http://localhost/a')
511        u2 = url.URL.fromString('http://localhost/b')
512        self.failUnless(u1 != u2, "%r == %r" % (u1, u2))
513
514    def test_ne_apples_vs_oranges(self):
515        u = url.URL.fromString('http://localhost/')
516        self.failUnless(u != 42, "URL must differ from a number.")
517        self.failUnless(u != object(), "URL must be differ from an object.")
518
519    def test_parseEqualInParamValue(self):
520        u = url.URL.fromString('http://localhost/?=x=x=x')
521        self.failUnless(u.query == ['=x=x=x'])
522        self.failUnless(str(u) == 'http://localhost/?=x%3Dx%3Dx')
523        u = url.URL.fromString('http://localhost/?foo=x=x=x&bar=y')
524        self.failUnless(u.query == ['foo=x=x=x', 'bar=y'])
525        self.failUnless(str(u) == 'http://localhost/?foo=x%3Dx%3Dx&bar=y')
526
527class Serialization(TestCase):
528
529    def testQuoting(self):
530        context = None
531        scheme = 'http'
532        loc = 'localhost'
533        path = ('baz', 'buz', '/fuzz/')
534        query = [("foo", "bar"), ("baz", "=quux"), ("foobar", "?")]
535        fragment = 'futz'
536        u = url.URL(scheme, loc, path, query, fragment)
537        s = flatten(url.URL(scheme, loc, path, query, fragment))
538
539        parsedScheme, parsedLoc, parsedPath, parsedQuery, parsedFragment = urlparse.urlsplit(s)
540
541        self.assertEquals(scheme, parsedScheme)
542        self.assertEquals(loc, parsedLoc)
543        self.assertEquals('/' + '/'.join(map(lambda p: urllib.quote(p,safe=''),path)), parsedPath)
544        self.assertEquals(query, url.unquerify(parsedQuery))
545        self.assertEquals(fragment, parsedFragment)
546
547    def test_slotQueryParam(self):
548        original = 'http://foo/bar?baz=bamf'
549        u = url.URL.fromString(original)
550        u = u.add('toot', tags.slot('param'))
551
552        def fillIt(ctx, data):
553            ctx.fillSlots('param', 5)
554            return ctx.tag
555
556        self.assertEquals(flatten(tags.invisible(render=fillIt)[u]), original + '&toot=5')
557
558    def test_childQueryParam(self):
559        original = 'http://foo/bar'
560        u = url.URL.fromString(original)
561        u = u.child(tags.slot('param'))
562
563        def fillIt(ctx, data):
564            ctx.fillSlots('param', 'baz')
565            return ctx.tag
566
567        self.assertEquals(flatten(tags.invisible(render=fillIt)[u]), original + '/baz')
568
569    def test_strangeSegs(self):
570        base = 'http://localhost/'
571        tests = (
572            (r'/foo/', '%2Ffoo%2F'),
573            (r'c:\foo\bar bar', 'c%3A%5Cfoo%5Cbar%20bar'),
574            (r'&<>', '%26%3C%3E'),
575            (u'!"\N{POUND SIGN}$%^&*()_+'.encode('utf-8'), '!%22%C2%A3%24%25%5E%26*()_%2B'),
576            )
577        for test, result in tests:
578            u = url.URL.fromString(base).child(test)
579            self.assertEquals(flatten(u), base+result)
580
581    def test_urlContent(self):
582        u = url.URL.fromString('http://localhost/').child(r'<c:\foo\bar&>')
583        self.assertEquals(flatten(tags.p[u]), '<p>http://localhost/%3Cc%3A%5Cfoo%5Cbar%26%3E</p>')
584
585    def test_urlAttr(self):
586        u = url.URL.fromString('http://localhost/').child(r'<c:\foo\bar&>')
587        self.assertEquals(flatten(tags.img(src=u)), '<img src="http://localhost/%3Cc%3A%5Cfoo%5Cbar%26%3E" />')
588
589    def test_urlSlot(self):
590        u = url.URL.fromString('http://localhost/').child(r'<c:\foo\bar&>')
591        tag = tags.img(src=tags.slot('src'))
592        tag.fillSlots('src', u)
593        self.assertEquals(flatten(tag), '<img src="http://localhost/%3Cc%3A%5Cfoo%5Cbar%26%3E" />')
594
595    def test_urlXmlAttrSlot(self):
596        u = url.URL.fromString('http://localhost/').child(r'<c:\foo\bar&>')
597        tag = tags.invisible[loaders.xmlstr('<img xmlns:n="http://nevow.com/ns/nevow/0.1" src="#"><n:attr name="src"><n:slot name="src"/></n:attr></img>')]
598        tag.fillSlots('src', u)
599        self.assertEquals(flatten(tag), '<img src="http://localhost/%3Cc%3A%5Cfoo%5Cbar%26%3E" />')
600
601    def test_safe(self):
602        u = url.URL.fromString('http://localhost/').child(r"foo-_.!*'()bar")
603        self.assertEquals(flatten(tags.p[u]), r"<p>http://localhost/foo-_.!*'()bar</p>")
604
605    def test_urlintagwithmultipleamps(self):
606        """
607        Test the serialization of an URL with an ampersand in it as an
608        attribute value.
609
610        The ampersand must be quoted for the attribute to be valid.
611        """
612        tag = tags.invisible[tags.a(href=url.URL.fromString('http://localhost/').add('foo', 'bar').add('baz', 'spam'))]
613        self.assertEquals(flatten(tag), '<a href="http://localhost/?foo=bar&amp;baz=spam"></a>')
614
615        tag = tags.invisible[loaders.xmlstr('<a xmlns:n="http://nevow.com/ns/nevow/0.1" href="#"><n:attr name="href"><n:slot name="href"/></n:attr></a>')]
616        tag.fillSlots('href', url.URL.fromString('http://localhost/').add('foo', 'bar').add('baz', 'spam'))
617        self.assertEquals(flatten(tag), '<a href="http://localhost/?foo=bar&amp;baz=spam"></a>')
618
619
620    def test_rfc1808(self):
621        """Test the relative link resolving stuff I found in rfc1808 section 5.
622        """
623        base = url.URL.fromString(rfc1808_relative_link_base)
624        for link, result in rfc1808_relative_link_tests:
625            #print link
626            self.failUnlessEqual(result, flatten(base.click(link)))
627    test_rfc1808.todo = 'Many of these fail miserably at the moment; often with a / where there shouldn\'t be'
628
629
630    def test_unicode(self):
631        """
632        L{URLSerializer} should provide basic IRI (RFC 3987) support by
633        encoding Unicode to UTF-8 before percent-encoding.
634        """
635        iri = u'http://localhost/expos\xe9?doppelg\xe4nger=Bryan O\u2019Sullivan#r\xe9sum\xe9'
636        uri = 'http://localhost/expos%C3%A9?doppelg%C3%A4nger=Bryan%20O%E2%80%99Sullivan#r%C3%A9sum%C3%A9'
637        self.assertEquals(flatten(url.URL.fromString(iri)), uri)
638
639
640
641class RedirectResource(TestCase):
642    """Test the url redirect resource adapters.
643    """
644
645    def renderResource(self, u):
646        request = FakeRequest()
647        ctx = context.RequestContext(tag=request)
648        return util.maybeDeferred(inevow.IResource(u).renderHTTP, ctx).addCallback(
649            lambda r: (r, request.redirected_to))
650
651
652    def test_urlRedirect(self):
653        u = "http://localhost/"
654        D = self.renderResource(url.URL.fromString(u))
655        def after((html, redirected_to)):
656            self.assertIn(u, html)
657            self.assertEquals(u, redirected_to)
658        return D.addCallback(after)
659
660
661    def test_urlRedirectWithParams(self):
662        D = self.renderResource(url.URL.fromString("http://localhost/").child('child').add('foo', 'bar'))
663        def after((html, redirected_to)):
664            self.assertIn("http://localhost/child?foo=bar", html)
665            self.assertEquals("http://localhost/child?foo=bar", redirected_to)
666        return D.addCallback(after)
667
668
669    def test_deferredURLParam(self):
670        D = self.renderResource(
671            url.URL.fromString("http://localhost/")
672            .child(util.succeed('child')).add('foo',util.succeed('bar'))
673            )
674        def after((html, redirected_to)):
675            self.assertIn("http://localhost/child?foo=bar", html)
676            self.assertEquals("http://localhost/child?foo=bar", redirected_to)
677        return D.addCallback(after)
678
679
680    def test_deferredURLOverlayParam(self):
681        D = self.renderResource(url.here.child(util.succeed('child')).add('foo',util.succeed('bar')))
682        def after((html, redirected_to)):
683            self.assertIn("http://localhost/child?foo=bar", html)
684            self.assertEquals("http://localhost/child?foo=bar", redirected_to)
685        return D.addCallback(after)
686
687