1 # (c) 2005 Ian Bicking, Clark C. Evans and contributors 2 # This module is part of the Python Paste Project and is released under 3 # the MIT License: http://www.opensource.org/licenses/mit-license.php 4 import time 5 import random 6 import os 7 import tempfile 8 try: 9 # Python 3 10 from email.utils import parsedate_tz, mktime_tz 11 except ImportError: 12 # Python 2 13 from rfc822 import parsedate_tz, mktime_tz 14 import six 15 16 from paste import fileapp 17 from paste.fileapp import * 18 from paste.fixture import * 19 20 # NOTE(haypo): don't use string.letters because the order of lower and upper 21 # case letters changes when locale.setlocale() is called for the first time 22 LETTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' 23 24 def test_data(): 25 harness = TestApp(DataApp(b'mycontent')) 26 res = harness.get("/") 27 assert 'application/octet-stream' == res.header('content-type') 28 assert '9' == res.header('content-length') 29 assert "<Response 200 OK 'mycontent'>" == repr(res) 30 harness.app.set_content(b"bingles") 31 assert "<Response 200 OK 'bingles'>" == repr(harness.get("/")) 32 33 def test_cache(): 34 def build(*args,**kwargs): 35 app = DataApp(b"SomeContent") 36 app.cache_control(*args,**kwargs) 37 return TestApp(app).get("/") 38 res = build() 39 assert 'public' == res.header('cache-control') 40 assert not res.header('expires',None) 41 res = build(private=True) 42 assert 'private' == res.header('cache-control') 43 assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time() 44 res = build(no_cache=True) 45 assert 'no-cache' == res.header('cache-control') 46 assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time() 47 res = build(max_age=60,s_maxage=30) 48 assert 'public, max-age=60, s-maxage=30' == res.header('cache-control') 49 expires = mktime_tz(parsedate_tz(res.header('expires'))) 50 assert expires > time.time()+58 and expires < time.time()+61 51 res = build(private=True, max_age=60, no_transform=True, no_store=True) 52 assert 'private, no-store, no-transform, max-age=60' == \ 53 res.header('cache-control') 54 expires = mktime_tz(parsedate_tz(res.header('expires'))) 55 assert mktime_tz(parsedate_tz(res.header('expires'))) < time.time() 56 57 def test_disposition(): 58 def build(*args,**kwargs): 59 app = DataApp(b"SomeContent") 60 app.content_disposition(*args,**kwargs) 61 return TestApp(app).get("/") 62 res = build() 63 assert 'attachment' == res.header('content-disposition') 64 assert 'application/octet-stream' == res.header('content-type') 65 res = build(filename="bing.txt") 66 assert 'attachment; filename="bing.txt"' == \ 67 res.header('content-disposition') 68 assert 'text/plain' == res.header('content-type') 69 res = build(inline=True) 70 assert 'inline' == res.header('content-disposition') 71 assert 'application/octet-stream' == res.header('content-type') 72 res = build(inline=True, filename="/some/path/bing.txt") 73 assert 'inline; filename="bing.txt"' == \ 74 res.header('content-disposition') 75 assert 'text/plain' == res.header('content-type') 76 try: 77 res = build(inline=True,attachment=True) 78 except AssertionError: 79 pass 80 else: 81 assert False, "should be an exception" 82 83 def test_modified(): 84 harness = TestApp(DataApp(b'mycontent')) 85 res = harness.get("/") 86 assert "<Response 200 OK 'mycontent'>" == repr(res) 87 last_modified = res.header('last-modified') 88 res = harness.get("/",headers={'if-modified-since': last_modified}) 89 assert "<Response 304 Not Modified ''>" == repr(res) 90 res = harness.get("/",headers={'if-modified-since': last_modified + \ 91 '; length=1506'}) 92 assert "<Response 304 Not Modified ''>" == repr(res) 93 res = harness.get("/",status=400, 94 headers={'if-modified-since': 'garbage'}) 95 assert 400 == res.status and b"ill-formed timestamp" in res.body 96 res = harness.get("/",status=400, 97 headers={'if-modified-since': 98 'Thu, 22 Dec 2030 01:01:01 GMT'}) 99 assert 400 == res.status and b"check your system clock" in res.body 100 101 def test_file(): 102 tempfile = "test_fileapp.%s.txt" % (random.random()) 103 content = LETTERS * 20 104 if six.PY3: 105 content = content.encode('utf8') 106 with open(tempfile, "wb") as fp: 107 fp.write(content) 108 try: 109 app = fileapp.FileApp(tempfile) 110 res = TestApp(app).get("/") 111 assert len(content) == int(res.header('content-length')) 112 assert 'text/plain' == res.header('content-type') 113 assert content == res.body 114 assert content == app.content # this is cashed 115 lastmod = res.header('last-modified') 116 print("updating", tempfile) 117 file = open(tempfile,"a+") 118 file.write("0123456789") 119 file.close() 120 res = TestApp(app).get("/",headers={'Cache-Control': 'max-age=0'}) 121 assert len(content)+10 == int(res.header('content-length')) 122 assert 'text/plain' == res.header('content-type') 123 assert content + b"0123456789" == res.body 124 assert app.content # we are still cached 125 file = open(tempfile,"a+") 126 file.write("X" * fileapp.CACHE_SIZE) # exceed the cashe size 127 file.write("YZ") 128 file.close() 129 res = TestApp(app).get("/",headers={'Cache-Control': 'max-age=0'}) 130 newsize = fileapp.CACHE_SIZE + len(content)+12 131 assert newsize == int(res.header('content-length')) 132 assert newsize == len(res.body) 133 assert res.body.startswith(content) and res.body.endswith(b'XYZ') 134 assert not app.content # we are no longer cached 135 finally: 136 os.unlink(tempfile) 137 138 def test_dir(): 139 tmpdir = tempfile.mkdtemp() 140 try: 141 tmpfile = os.path.join(tmpdir, 'file') 142 tmpsubdir = os.path.join(tmpdir, 'dir') 143 fp = open(tmpfile, 'w') 144 fp.write('abcd') 145 fp.close() 146 os.mkdir(tmpsubdir) 147 try: 148 app = fileapp.DirectoryApp(tmpdir) 149 for path in ['/', '', '//', '/..', '/.', '/../..']: 150 assert TestApp(app).get(path, status=403).status == 403, ValueError(path) 151 for path in ['/~', '/foo', '/dir', '/dir/']: 152 assert TestApp(app).get(path, status=404).status == 404, ValueError(path) 153 assert TestApp(app).get('/file').body == b'abcd' 154 finally: 155 os.remove(tmpfile) 156 os.rmdir(tmpsubdir) 157 finally: 158 os.rmdir(tmpdir) 159 160 def _excercize_range(build,content): 161 # full content request, but using ranges' 162 res = build("bytes=0-%d" % (len(content)-1)) 163 assert res.header('accept-ranges') == 'bytes' 164 assert res.body == content 165 assert res.header('content-length') == str(len(content)) 166 res = build("bytes=-%d" % (len(content)-1)) 167 assert res.body == content 168 assert res.header('content-length') == str(len(content)) 169 res = build("bytes=0-") 170 assert res.body == content 171 assert res.header('content-length') == str(len(content)) 172 # partial content requests 173 res = build("bytes=0-9", status=206) 174 assert res.body == content[:10] 175 assert res.header('content-length') == '10' 176 res = build("bytes=%d-" % (len(content)-1), status=206) 177 assert res.body == b'Z' 178 assert res.header('content-length') == '1' 179 res = build("bytes=%d-%d" % (3,17), status=206) 180 assert res.body == content[3:18] 181 assert res.header('content-length') == '15' 182 183 def test_range(): 184 content = LETTERS * 5 185 if six.PY3: 186 content = content.encode('utf8') 187 def build(range, status=206): 188 app = DataApp(content) 189 return TestApp(app).get("/",headers={'Range': range}, status=status) 190 _excercize_range(build,content) 191 build('bytes=0-%d' % (len(content)+1), 416) 192 193 def test_file_range(): 194 tempfile = "test_fileapp.%s.txt" % (random.random()) 195 content = LETTERS * (1+(fileapp.CACHE_SIZE // len(LETTERS))) 196 if six.PY3: 197 content = content.encode('utf8') 198 assert len(content) > fileapp.CACHE_SIZE 199 with open(tempfile, "wb") as fp: 200 fp.write(content) 201 try: 202 def build(range, status=206): 203 app = fileapp.FileApp(tempfile) 204 return TestApp(app).get("/",headers={'Range': range}, 205 status=status) 206 _excercize_range(build,content) 207 for size in (13,len(LETTERS), len(LETTERS)-1): 208 fileapp.BLOCK_SIZE = size 209 _excercize_range(build,content) 210 finally: 211 os.unlink(tempfile) 212 213 def test_file_cache(): 214 filename = os.path.join(os.path.dirname(__file__), 215 'urlparser_data', 'secured.txt') 216 app = TestApp(fileapp.FileApp(filename)) 217 res = app.get('/') 218 etag = res.header('ETag') 219 last_mod = res.header('Last-Modified') 220 res = app.get('/', headers={'If-Modified-Since': last_mod}, 221 status=304) 222 res = app.get('/', headers={'If-None-Match': etag}, 223 status=304) 224 res = app.get('/', headers={'If-None-Match': 'asdf'}, 225 status=200) 226 res = app.get('/', headers={'If-Modified-Since': 'Sat, 1 Jan 2005 12:00:00 GMT'}, 227 status=200) 228 res = app.get('/', headers={'If-Modified-Since': last_mod + '; length=100'}, 229 status=304) 230 res = app.get('/', headers={'If-Modified-Since': 'invalid date'}, 231 status=400) 232 233 def test_methods(): 234 filename = os.path.join(os.path.dirname(__file__), 235 'urlparser_data', 'secured.txt') 236 app = TestApp(fileapp.FileApp(filename)) 237 get_res = app.get('') 238 res = app.get('', extra_environ={'REQUEST_METHOD': 'HEAD'}) 239 assert res.headers == get_res.headers 240 assert not res.body 241 app.post('', status=405) # Method Not Allowed 242 243