Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
Choose a Base Repository
Pylons/webob
0xngold/webob
AlenJun/webob
B-Rich/webob
Batterii/webob
Codonaut/webob
Craga89/webob
CreamCoderz/webob
FashtimeDotCom/webob
GordonSchmidt/webob
Ivoz/webob
JelleZijlstra/webob
Mereostr/webob
MiCHiLU/webob
Natim/webob
PlumpMath/webob
ViktorL/webob
YuanrongZheng/webob
adamchainz/webob
alertedsnake/webob
almet/webob
amol-/webob
amotl/webob
anaer/webob
aodag/webob
asottile/webob
bertjwregeer/webob
brotchie/webob
bukzor/webob
cdellin/webob
cdunklau/webob
chimobb/webob
chris-morgan/webob
chrismorales/webob
cjford/webob
coltonlw/webob
cwebber/webob
cymoo/webob
dairiki/webob
danielholmstrom/webob
deuxpi/webob
devIORA/webob
dirkmueller/webob
dobesv/webob
domenkozar/webob
doulbekill/webob
dstufft/webob
e42s/webob
echlebek/webob
faassen/webob
fortara/webob
google-feinberg/webob
gosom/webob
gotostack/webob
greghaynes/webob
hayate/webob
htang2013/webob
hzweveryday/webob
ianb/webob
invisibleroads/webob
inytar/webob
jd/webob
jeremydw/webob
jimbaker/webob
jinty/webob
jonatasoliveira/webob
joulez/webob
jturmel/webob
julienmeyer/webob
k-hasan-19/webob
koansys/webob
kthguru/webob
lakshmi-kannan/webob
lasson/webob
lieryan/webob
lmctv/webob
lrowe/webob
ltvolks/webob
lukecyca/webob
madhawa1206/webob
maisano/webob
martinth/webob
mateuszklimek/webob
melon-li/webob
metalman/webob
meteogrid/webob
minddistrict/webob
miohtama/webob
mitchellrj/webob
moriyoshi/webob
mpirnat/webob
mrmuxl/webob
msabramo/webob
mvidner/webob
narendasan/webob
nickstenning/webob
nkunal/webob
nphilipp/webob
oas89/webob
openprocurement/webob
panpan-zhang/webob
perey/webob
pombredanne/webob
proppy/webob
pubkraal/webob
py-web/webob
qq40660/webob
quantum-omega/webob
rclmenezes/webob
renyidong/webob
rfk/webob
rjungbeck/webob
rogerioyuuki/webob
ryanpetrello/webob
rylz/webob
saschpe/webob
shashankchakelam/webob
sigmavirus24/webob
sileht/webob
simudream/webob
smal/webob
sontek/webob
squirro/webob
stevepiercy/webob
sunyi00/webob
tbkraf08/webob
tc1989tc/webob
thefuture2092/webob
tjxiter/webob
toanant/webob
tshepang/webob
wdvill/webob
wenxueliu/webob
whiteroses/webob
witsch/webob
xpahos/webob
xuweiwei2011/webob
yoniLavi/webob
young001/webob
yws/webob
zuiwanting/webob-Pylons
Nothing to show
...
Choose a Head Repository
Pylons/webob
0xngold/webob
AlenJun/webob
B-Rich/webob
Batterii/webob
Codonaut/webob
Craga89/webob
CreamCoderz/webob
FashtimeDotCom/webob
GordonSchmidt/webob
Ivoz/webob
JelleZijlstra/webob
Mereostr/webob
MiCHiLU/webob
Natim/webob
PlumpMath/webob
ViktorL/webob
YuanrongZheng/webob
adamchainz/webob
alertedsnake/webob
almet/webob
amol-/webob
amotl/webob
anaer/webob
aodag/webob
asottile/webob
bertjwregeer/webob
brotchie/webob
bukzor/webob
cdellin/webob
cdunklau/webob
chimobb/webob
chris-morgan/webob
chrismorales/webob
cjford/webob
coltonlw/webob
cwebber/webob
cymoo/webob
dairiki/webob
danielholmstrom/webob
deuxpi/webob
devIORA/webob
dirkmueller/webob
dobesv/webob
domenkozar/webob
doulbekill/webob
dstufft/webob
e42s/webob
echlebek/webob
faassen/webob
fortara/webob
google-feinberg/webob
gosom/webob
gotostack/webob
greghaynes/webob
hayate/webob
htang2013/webob
hzweveryday/webob
ianb/webob
invisibleroads/webob
inytar/webob
jd/webob
jeremydw/webob
jimbaker/webob
jinty/webob
jonatasoliveira/webob
joulez/webob
jturmel/webob
julienmeyer/webob
k-hasan-19/webob
koansys/webob
kthguru/webob
lakshmi-kannan/webob
lasson/webob
lieryan/webob
lmctv/webob
lrowe/webob
ltvolks/webob
lukecyca/webob
madhawa1206/webob
maisano/webob
martinth/webob
mateuszklimek/webob
melon-li/webob
metalman/webob
meteogrid/webob
minddistrict/webob
miohtama/webob
mitchellrj/webob
moriyoshi/webob
mpirnat/webob
mrmuxl/webob
msabramo/webob
mvidner/webob
narendasan/webob
nickstenning/webob
nkunal/webob
nphilipp/webob
oas89/webob
openprocurement/webob
panpan-zhang/webob
perey/webob
pombredanne/webob
proppy/webob
pubkraal/webob
py-web/webob
qq40660/webob
quantum-omega/webob
rclmenezes/webob
renyidong/webob
rfk/webob
rjungbeck/webob
rogerioyuuki/webob
ryanpetrello/webob
rylz/webob
saschpe/webob
shashankchakelam/webob
sigmavirus24/webob
sileht/webob
simudream/webob
smal/webob
sontek/webob
squirro/webob
stevepiercy/webob
sunyi00/webob
tbkraf08/webob
tc1989tc/webob
thefuture2092/webob
tjxiter/webob
toanant/webob
tshepang/webob
wdvill/webob
wenxueliu/webob
whiteroses/webob
witsch/webob
xpahos/webob
xuweiwei2011/webob
yoniLavi/webob
young001/webob
yws/webob
zuiwanting/webob-Pylons
Nothing to show
  • 16 commits
  • 2 files changed
  • 2 commit comments
  • 2 contributors
Commits on Mar 03, 2012
Commits on Mar 04, 2012
Commits on Mar 05, 2012
Commits on Mar 27, 2012
Commits on Apr 02, 2012
Remove .static.FileApp.update() method, it is not needed
The module doesn't do caching anymore, and it was related to caching,
so, let's get rid of it.
Commits on Apr 04, 2012
Commits on Apr 11, 2012
Commits on Apr 12, 2012
Sergey Schetinin
Showing with 329 additions and 0 deletions.
  1. +205 −0 tests/test_static.py
  2. +124 −0 webob/static.py
View
@@ -0,0 +1,205 @@
+from io import BytesIO
+from os.path import getmtime
+import tempfile
+from time import gmtime
+import os
+import shutil
+import unittest
+
+from webob import static
+from webob.compat import bytes_
+from webob.request import Request, environ_from_url
+from webob.response import Response
+
+
+def get_response(app, path='/', **req_kw):
+ """Convenient function to query an application"""
+ req = Request(environ_from_url(path), **req_kw)
+ return req.get_response(app)
+
+
+def create_file(content, *paths):
+ """Convenient function to create a new file with some content"""
+ path = os.path.join(*paths)
+ with open(path, 'wb') as fp:
+ fp.write(bytes_(content))
+ return path
+
+
+class TestFileApp(unittest.TestCase):
+ def setUp(self):
+ fp = tempfile.NamedTemporaryFile(suffix=".py", delete=False)
+ self.tempfile = fp.name
+ fp.write(b"import this\n")
+ fp.close()
+
+ def tearDown(self):
+ os.unlink(self.tempfile)
+
+ def test_fileapp(self):
+ app = static.FileApp(self.tempfile)
+ resp1 = get_response(app)
+ self.assertEqual(resp1.content_type, 'text/x-python')
+ self.assertEqual(resp1.charset, 'UTF-8')
+ self.assertEqual(resp1.last_modified.timetuple(), gmtime(getmtime(self.tempfile)))
+
+ resp2 = get_response(app)
+ self.assertEqual(resp2.content_type, 'text/x-python')
+ self.assertEqual(resp2.last_modified.timetuple(), gmtime(getmtime(self.tempfile)))
+
+ resp3 = get_response(app, range=(7, 11))
+ self.assertEqual(resp3.status_int, 206)
+ self.assertEqual(tuple(resp3.content_range)[:2], (7, 11))
+ self.assertEqual(resp3.last_modified.timetuple(), gmtime(getmtime(self.tempfile)))
+ self.assertEqual(resp3.body, bytes_('this'))
+
+ def test_unexisting_file(self):
+ app = static.FileApp('/tmp/this/doesnt/exist')
+ self.assertEqual(404, get_response(app).status_int)
+
+ def test_allowed_methods(self):
+ app = static.FileApp(self.tempfile)
+
+ # Alias
+ resp = lambda method: get_response(app, method=method)
+
+ self.assertEqual(200, resp(method='GET').status_int)
+ self.assertEqual(200, resp(method='HEAD').status_int)
+ self.assertEqual(405, resp(method='POST').status_int)
+ # Actually any other method is not allowed
+ self.assertEqual(405, resp(method='xxx').status_int)
+
+ def test_exception_while_opening_file(self):
+ # Mock the built-in ``open()`` function to allow finner control about
+ # what we are testing.
+ def open_ioerror(*args, **kwargs):
+ raise IOError()
+
+ def open_oserror(*args, **kwargs):
+ raise OSError()
+
+ app = static.FileApp(self.tempfile)
+ old_open = __builtins__['open']
+
+ try:
+ __builtins__['open'] = open_ioerror
+ self.assertEqual(403, get_response(app).status_int)
+
+ __builtins__['open'] = open_oserror
+ self.assertEqual(403, get_response(app).status_int)
+ finally:
+ __builtins__['open'] = old_open
+
+
+class TestFileIter(unittest.TestCase):
+ def test_empty_file(self):
+ fp = BytesIO()
+ fi = static.FileIter(fp)
+ self.assertRaises(StopIteration, next, iter(fi))
+
+ def test_seek(self):
+ fp = BytesIO(bytes_("0123456789"))
+ i = static.FileIter(fp).app_iter_range(seek=4)
+
+ self.assertEqual(bytes_("456789"), next(i))
+ self.assertRaises(StopIteration, next, i)
+
+ def test_limit(self):
+ fp = BytesIO(bytes_("0123456789"))
+ i = static.FileIter(fp).app_iter_range(limit=4)
+
+ self.assertEqual(bytes_("0123"), next(i))
+ self.assertRaises(StopIteration, next, i)
+
+ def test_limit_and_seek(self):
+ fp = BytesIO(bytes_("0123456789"))
+ i = static.FileIter(fp).app_iter_range(limit=4, seek=1)
+
+ self.assertEqual(bytes_("123"), next(i))
+ self.assertRaises(StopIteration, next, i)
+
+ def test_multiple_reads(self):
+ fp = BytesIO(bytes_("012"))
+ i = static.FileIter(fp).app_iter_range(block_size=1)
+
+ self.assertEqual(bytes_("0"), next(i))
+ self.assertEqual(bytes_("1"), next(i))
+ self.assertEqual(bytes_("2"), next(i))
+ self.assertRaises(StopIteration, next, i)
+
+ def test_seek_bigger_than_limit(self):
+ fp = BytesIO(bytes_("0123456789"))
+ i = static.FileIter(fp).app_iter_range(limit=1, seek=2)
+
+ # XXX: this should not return anything actually, since we are starting
+ # to read after the place we wanted to stop.
+ self.assertEqual(bytes_("23456789"), next(i))
+ self.assertRaises(StopIteration, next, i)
+
+ def test_limit_is_zero(self):
+ fp = BytesIO(bytes_("0123456789"))
+ i = static.FileIter(fp).app_iter_range(limit=0)
+
+ self.assertRaises(StopIteration, next, i)
+
+
+
+class TestDirectoryApp(unittest.TestCase):
+ def setUp(self):
+ self.test_dir = tempfile.mkdtemp()
+
+ def tearDown(self):
+ shutil.rmtree(self.test_dir)
+
+ def test_empty_directory(self):
+ app = static.DirectoryApp(self.test_dir)
+ self.assertEqual(404, get_response(app).status_int)
+ self.assertEqual(404, get_response(app, '/foo').status_int)
+
+ def test_serve_file(self):
+ app = static.DirectoryApp(self.test_dir)
+ create_file('abcde', self.test_dir, 'bar')
+ self.assertEqual(404, get_response(app).status_int)
+ self.assertEqual(404, get_response(app, '/foo').status_int)
+
+ resp = get_response(app, '/bar')
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual(bytes_('abcde'), resp.body)
+
+ def test_dont_serve_file_in_parent_directory(self):
+ # We'll have:
+ # /TEST_DIR/
+ # /TEST_DIR/bar
+ # /TEST_DIR/foo/ <- serve this directory
+ create_file('abcde', self.test_dir, 'bar')
+ serve_path = os.path.join(self.test_dir, 'foo')
+ os.mkdir(serve_path)
+ app = static.DirectoryApp(serve_path)
+
+ # The file exists, but is outside the served dir.
+ self.assertEqual(403, get_response(app, '/../bar').status_int)
+
+ def test_file_app_arguments(self):
+ app = static.DirectoryApp(self.test_dir, content_type='xxx/yyy')
+ create_file('abcde', self.test_dir, 'bar')
+
+ resp = get_response(app, '/bar')
+ self.assertEqual(200, resp.status_int)
+ self.assertEqual('xxx/yyy', resp.content_type)
+
+ def test_file_app_factory(self):
+ def make_fileapp(*args, **kwargs):
+ make_fileapp.called = True
+ return Response()
+ make_fileapp.called = False
+
+ app = static.DirectoryApp(self.test_dir)
+ app.make_fileapp = make_fileapp
+ create_file('abcde', self.test_dir, 'bar')
+
+ get_response(app, '/bar')
+ self.assertTrue(make_fileapp.called)
+
+ def test_must_serve_directory(self):
+ serve_path = create_file('abcde', self.test_dir, 'bar')
+ self.assertRaises(AssertionError, static.DirectoryApp, serve_path)
View
@@ -0,0 +1,124 @@
+import mimetypes
+import os
+
+from webob import exc
+from webob.dec import wsgify
+from webob.response import Response
+
+__all__ = [
+ 'FileApp', 'DirectoryApp',
+]
+
+mimetypes._winreg = None # do not load mimetypes from windows registry
+mimetypes.add_type('text/javascript', '.js') # stdlib default is application/x-javascript
+mimetypes.add_type('image/x-icon', '.ico') # not among defaults
+
+
+class FileApp(object):
+ """An application that will send the file at the given filename.
+
+ Adds a mime type based on `mimetypes.guess_type()`.
+ """
+
+ def __init__(self, filename, **kw):
+ self.filename = filename
+ content_type, content_encoding = mimetypes.guess_type(filename)
+ kw.setdefault('content_type', content_type)
+ kw.setdefault('content_encoding', content_encoding)
+ kw.setdefault('accept_ranges', 'bytes')
+ self.kw = kw
+
+ @wsgify
+ def __call__(self, req):
+ if req.method not in ('GET', 'HEAD'):
+ return exc.HTTPMethodNotAllowed("You cannot %s a file" %
+ req.method)
+ try:
+ stat = os.stat(self.filename)
+ except (IOError, OSError) as e:
+ msg = "Can't open %r: %s" % (self.filename, e)
+ return exc.HTTPNotFound(comment=msg)
+
+ try:
+ file = open(self.filename, 'rb')
+ except (IOError, OSError) as e:
+ msg = "You are not permitted to view this file (%s)" % e
+ return exc.HTTPForbidden(msg)
+
+ return Response(
+ app_iter = FileIter(file),
+ content_length = stat.st_size,
+ last_modified = stat.st_mtime,
+ #@@ etag
+ **self.kw
+ ).conditional_response_app
+
+
+class FileIter(object):
+ def __init__(self, file):
+ self.file = file
+
+ def app_iter_range(self, seek=None, limit=None, block_size=1<<16):
+ """Iter over the content of the file.
+
+ You can set the `seek` parameter to read the file starting from a
+ specific position.
+
+ You can set the `limit` parameter to read the file up to specific
+ position.
+
+ Finally, you can change the number of bytes read at once by setting the
+ `block_size` parameter.
+ """
+
+ if seek:
+ self.file.seek(seek)
+ if limit is not None:
+ limit -= seek
+ try:
+ while True:
+ data = self.file.read(min(block_size, limit)
+ if limit is not None
+ else block_size)
+ if not data:
+ return
+ yield data
+ if limit is not None:
+ limit -= len(data)
+ if limit <= 0:
+ return
+ finally:
+ self.file.close()
+
+ __iter__ = app_iter_range
+
+
+class DirectoryApp(object):
+ """An application that dispatches requests to corresponding `FileApp`s based
+ on PATH_INFO.
+
+ This app double-checks not to serve any files that are not in a
+ subdirectory. To customize `FileApp` instances creation, override
+ `make_fileapp` method.
+ """
+
+ def __init__(self, path, **kw):
+ self.path = os.path.abspath(path)
+ if not self.path.endswith(os.path.sep):
+ self.path += os.path.sep
+ assert os.path.isdir(self.path)
+ self.fileapp_kw = kw
+
+ def make_fileapp(self, path):
+ return FileApp(path, **self.fileapp_kw)
+
+ @wsgify
+ def __call__(self, req):
+ path = os.path.abspath(os.path.join(self.path,
+ req.path_info.lstrip('/')))
+ if not os.path.isfile(path):
+ return exc.HTTPNotFound(comment=path)
+ elif not path.startswith(self.path):
+ return exc.HTTPForbidden()
+ else:
+ return self.make_fileapp(path)

Showing you all comments on commits in this comparison.

Owner

mcdonc commented on 2f7e0cd Apr 12, 2012

Note that applying this commit caused the test suite to start to error out:

http://lists.repoze.org/pipermail/pyramid-checkins/2012-April/003847.html

It is also not at 100% coverage anymore, although I don't know that it's due to this commit.

Contributor

multani commented on 2f7e0cd Apr 12, 2012

I proposed a fix for the failure on Pypy.

As for the coverage, it's probably due to 17dd40e