1import os
2from paste.urlparser import *
3from paste.fixture import *
4from pkg_resources import get_distribution
5
6def relative_path(name):
7    here = os.path.join(os.path.dirname(os.path.abspath(__file__)),
8                        'urlparser_data')
9    f = os.path.join('urlparser_data', '..', 'urlparser_data', name)
10    return os.path.join(here, f)
11
12def path(name):
13    return os.path.join(os.path.dirname(os.path.abspath(__file__)),
14                        'urlparser_data', name)
15
16def make_app(name):
17    app = URLParser({}, path(name), name, index_names=['index', 'Main'])
18    testapp = TestApp(app)
19    return testapp
20
21def test_find_file():
22    app = make_app('find_file')
23    res = app.get('/')
24    assert 'index1' in res
25    assert res.header('content-type') == 'text/plain'
26    res = app.get('/index')
27    assert 'index1' in res
28    assert res.header('content-type') == 'text/plain'
29    res = app.get('/index.txt')
30    assert 'index1' in res
31    assert res.header('content-type') == 'text/plain'
32    res = app.get('/test2.html')
33    assert 'test2' in res
34    assert res.header('content-type') == 'text/html'
35    res = app.get('/test 3.html')
36    assert 'test 3' in res
37    assert res.header('content-type') == 'text/html'
38    res = app.get('/test%203.html')
39    assert 'test 3' in res
40    assert res.header('content-type') == 'text/html'
41    res = app.get('/dir with spaces/test 4.html')
42    assert 'test 4' in res
43    assert res.header('content-type') == 'text/html'
44    res = app.get('/dir%20with%20spaces/test%204.html')
45    assert 'test 4' in res
46    assert res.header('content-type') == 'text/html'
47    # Ensure only data under the app's root directory is accessible
48    res = app.get('/../secured.txt', status=404)
49    res = app.get('/dir with spaces/../../secured.txt', status=404)
50    res = app.get('/%2e%2e/secured.txt', status=404)
51    res = app.get('/%2e%2e%3fsecured.txt', status=404)
52    res = app.get('/..%3fsecured.txt', status=404)
53    res = app.get('/dir%20with%20spaces/%2e%2e/%2e%2e/secured.txt', status=404)
54
55def test_deep():
56    app = make_app('deep')
57    res = app.get('/')
58    assert 'index2' in res
59    res = app.get('/sub')
60    assert res.status == 301
61    print(res)
62    assert res.header('location') == 'http://localhost/sub/'
63    assert 'http://localhost/sub/' in res
64    res = app.get('/sub/')
65    assert 'index3' in res
66
67def test_python():
68    app = make_app('python')
69    res = app.get('/simpleapp')
70    assert 'test1' in res
71    assert res.header('test-header') == 'TEST!'
72    assert res.header('content-type') == 'text/html'
73    res = app.get('/stream')
74    assert 'test2' in res
75    res = app.get('/sub/simpleapp')
76    assert 'subsimple' in res
77
78def test_hook():
79    app = make_app('hook')
80    res = app.get('/bob/app')
81    assert 'user: bob' in res
82    res = app.get('/tim/')
83    assert 'index: tim' in res
84
85def test_not_found_hook():
86    app = make_app('not_found')
87    res = app.get('/simple/notfound')
88    assert res.status == 200
89    assert 'not found' in res
90    res = app.get('/simple/found')
91    assert 'is found' in res
92    res = app.get('/recur/__notfound', status=404)
93    # @@: It's unfortunate that the original path doesn't actually show up
94    assert '/recur/notfound' in res
95    res = app.get('/recur/__isfound')
96    assert res.status == 200
97    assert 'is found' in res
98    res = app.get('/user/list')
99    assert 'user: None' in res
100    res = app.get('/user/bob/list')
101    assert res.status == 200
102    assert 'user: bob' in res
103
104def test_relative_path_in_static_parser():
105    x = relative_path('find_file')
106    app = StaticURLParser(relative_path('find_file'))
107    assert '..' not in app.root_directory
108
109def test_xss():
110    app = TestApp(StaticURLParser(relative_path('find_file')),
111                  extra_environ={'HTTP_ACCEPT': 'text/html'})
112    res = app.get("/-->%0D<script>alert('xss')</script>", status=404)
113    assert b'--><script>' not in res.body
114
115def test_static_parser():
116    app = StaticURLParser(path('find_file'))
117    testapp = TestApp(app)
118    res = testapp.get('', status=301)
119    res = testapp.get('/', status=404)
120    res = testapp.get('/index.txt')
121    assert res.body.strip() == b'index1'
122    res = testapp.get('/index.txt/foo', status=404)
123    res = testapp.get('/test 3.html')
124    assert res.body.strip() == b'test 3'
125    res = testapp.get('/test%203.html')
126    assert res.body.strip() == b'test 3'
127    res = testapp.get('/dir with spaces/test 4.html')
128    assert res.body.strip() == b'test 4'
129    res = testapp.get('/dir%20with%20spaces/test%204.html')
130    assert res.body.strip() == b'test 4'
131    # Ensure only data under the app's root directory is accessible
132    res = testapp.get('/../secured.txt', status=404)
133    res = testapp.get('/dir with spaces/../../secured.txt', status=404)
134    res = testapp.get('/%2e%2e/secured.txt', status=404)
135    res = testapp.get('/dir%20with%20spaces/%2e%2e/%2e%2e/secured.txt', status=404)
136    res = testapp.get('/dir%20with%20spaces/', status=404)
137
138def test_egg_parser():
139    app = PkgResourcesParser('Paste', 'paste')
140    testapp = TestApp(app)
141    res = testapp.get('', status=301)
142    res = testapp.get('/', status=404)
143    res = testapp.get('/flup_session', status=404)
144    res = testapp.get('/util/classinit.py')
145    assert 'ClassInitMeta' in res
146    res = testapp.get('/util/classinit', status=404)
147    res = testapp.get('/util', status=301)
148    res = testapp.get('/util/classinit.py/foo', status=404)
149
150    # Find a readable file in the Paste pkg's root directory (or upwards the
151    # directory tree). Ensure it's not accessible via the URLParser
152    unreachable_test_file = None
153    search_path = pkg_root_path = get_distribution('Paste').location
154    level = 0
155    # We might not find any readable files in the pkg's root directory (this
156    # is likely when Paste is installed as a .egg in site-packages). We
157    # (hopefully) can prevent this by traversing up the directory tree until
158    # a usable file is found
159    while unreachable_test_file is None and \
160            os.path.normpath(search_path) != os.path.sep:
161        for file in os.listdir(search_path):
162            full_path = os.path.join(search_path, file)
163            if os.path.isfile(full_path) and os.access(full_path, os.R_OK):
164                unreachable_test_file = file
165                break
166
167        search_path = os.path.dirname(search_path)
168        level += 1
169    assert unreachable_test_file is not None, \
170           'test_egg_parser requires a readable file in a parent dir of the\n' \
171           'Paste pkg\'s root dir:\n%s' % pkg_root_path
172
173    unreachable_path = '/' + '../'*level + unreachable_test_file
174    unreachable_path_quoted = '/' + '%2e%2e/'*level + unreachable_test_file
175    res = testapp.get(unreachable_path, status=404)
176    res = testapp.get('/util/..' + unreachable_path, status=404)
177    res = testapp.get(unreachable_path_quoted, status=404)
178    res = testapp.get('/util/%2e%2e' + unreachable_path_quoted, status=404)
179