summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-09-06 21:19:08 -0400
committerChris McDonough <chrism@plope.com>2011-09-06 21:19:08 -0400
commitdc98227171b49237d5236a2fbac0889b7cc07da4 (patch)
tree5c6b31626b17b464c04b21bfb904bd2730100218
parentf506adb6a9c5e4dd867c92cc9ae1edb558ec131d (diff)
parent315f755fd8c04e90d01c14a97afae8778cf45f05 (diff)
downloadpyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.tar.gz
pyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.tar.bz2
pyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.zip
Merge branch 'feature.staticfilegeddon'
-rw-r--r--CHANGES.txt14
-rw-r--r--TODO.txt9
-rw-r--r--pyramid/static.py209
-rw-r--r--pyramid/tests/pkgs/static_abspath/__init__.py7
-rw-r--r--pyramid/tests/pkgs/static_assetspec/__init__.py3
-rw-r--r--pyramid/tests/test_config/test_views.py15
-rw-r--r--pyramid/tests/test_integration.py225
-rw-r--r--pyramid/tests/test_static.py491
-rw-r--r--pyramid/tests/test_traversal.py3
-rw-r--r--pyramid/tests/test_view.py62
-rw-r--r--pyramid/traversal.py3
-rw-r--r--pyramid/view.py16
12 files changed, 508 insertions, 549 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index ab02749d2..a4f3a4a43 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -10,6 +10,20 @@ Internal
- Fixed test suite; on some systems tests would fail due to indeterminate
test run ordering and a double-push-single-pop of a shared test variable.
+- Replaced use of ``paste.urlparser.StaticURLParser`` with a derivative of Chris Rossi's "happy" static file serving code.
+
+Behavior Differences
+--------------------
+
+- An ETag header is no longer set when serving a static file. A
+ Last-Modified header is set instead.
+
+- Static file serving no longer supports the ``wsgi.file_wrapper`` extension.
+
+- Instead of returning a ``403 Forbidden`` error when a static file is served
+ that cannot be accessed by the Pyramid process' user due to file
+ permissions, an IOError (or similar) will be raised.
+
1.2a5 (2011-09-04)
==================
diff --git a/TODO.txt b/TODO.txt
index 5305dcc57..4da189bce 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -7,6 +7,8 @@ Should-Have
- Consider adding exclog to all scaffolds to print tracebacks to the console
while the debug toolbar is enabled.
+- Add cache_max_age=3600 to add_static_view of all scaffolds.
+
Nice-to-Have
------------
@@ -79,13 +81,6 @@ Future
- 1.3: Add a default-view-config-params decorator that can be applied to a
class which names defaults for method-based view_config decorator options.
-- 1.3: - Eliminate non-deployment-non-scaffold-related Paste dependency:
- ``paste.urlparser.StaticURLParser`` (cutnpaste or reimplement, possibly
- using chrisrossi's happy stuff as a base). paste.urlparser/paste.fileapp
- features missing from happy.static: ``wsgi.file_wrapper`` support
- (FileApp.get), 'HEAD' method support (FileApp.get), ETAG and if-none-match
- support (DataApp.get), handling file permission exceptions (FileApp.get),
-
- 1.3: use zope.registry rather than zope.component.
- 1.4: Remove ``chameleon_text`` / ``chameleon_zpt`` deprecated functions
diff --git a/pyramid/static.py b/pyramid/static.py
index 1291ae58f..357fe8014 100644
--- a/pyramid/static.py
+++ b/pyramid/static.py
@@ -1,82 +1,70 @@
-import os
-import pkg_resources
+from os.path import normcase, normpath, join, getmtime, getsize, isdir, exists
+from pkg_resources import resource_exists, resource_filename, resource_isdir
+import mimetypes
-from paste import httpexceptions
-from paste import request
-from paste.httpheaders import ETAG
-from paste.urlparser import StaticURLParser
+from repoze.lru import lru_cache
from pyramid.asset import resolve_asset_spec
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.httpexceptions import HTTPMovedPermanently
from pyramid.path import caller_package
-from pyramid.request import call_app_with_subpath_as_path_info
-
-class PackageURLParser(StaticURLParser):
- """ This probably won't work with zipimported resources """
- def __init__(self, package_name, resource_name, root_resource=None,
- cache_max_age=None):
- self.package_name = package_name
- self.resource_name = os.path.normpath(resource_name)
- if root_resource is None:
- root_resource = self.resource_name
- self.root_resource = root_resource
- self.cache_max_age = cache_max_age
-
- def __call__(self, environ, start_response):
- path_info = environ.get('PATH_INFO', '')
- if not path_info:
- return self.add_slash(environ, start_response)
- if path_info == '/':
- # @@: This should obviously be configurable
- filename = 'index.html'
- else:
- filename = request.path_info_pop(environ)
- resource = os.path.normcase(os.path.normpath(
- self.resource_name + '/' + filename))
- if not resource.startswith(self.root_resource):
- # Out of bounds
- return self.not_found(environ, start_response)
- if not pkg_resources.resource_exists(self.package_name, resource):
- return self.not_found(environ, start_response)
- if pkg_resources.resource_isdir(self.package_name, resource):
- # @@: Cache?
- return self.__class__(
- self.package_name, resource, root_resource=self.resource_name,
- cache_max_age=self.cache_max_age)(environ, start_response)
- pi = environ.get('PATH_INFO')
- if pi and pi != '/':
- return self.error_extra_path(environ, start_response)
- full = pkg_resources.resource_filename(self.package_name, resource)
- if_none_match = environ.get('HTTP_IF_NONE_MATCH')
- if if_none_match:
- mytime = os.stat(full).st_mtime
- if str(mytime) == if_none_match:
- headers = []
- ETAG.update(headers, mytime)
- start_response('304 Not Modified', headers)
- return [''] # empty body
-
- fa = self.make_app(full)
- if self.cache_max_age:
- fa.cache_control(max_age=self.cache_max_age)
- return fa(environ, start_response)
-
- def not_found(self, environ, start_response, debug_message=None):
- comment=('SCRIPT_NAME=%r; PATH_INFO=%r; looking in package %s; '
- 'subdir %s ;debug: %s' % (environ.get('SCRIPT_NAME'),
- environ.get('PATH_INFO'),
- self.package_name,
- self.resource_name,
- debug_message or '(none)'))
- exc = httpexceptions.HTTPNotFound(
- 'The resource at %s could not be found'
- % request.construct_url(environ),
- comment=comment)
- return exc.wsgi_application(environ, start_response)
-
- def __repr__(self):
- return '<%s %s:%s at %s>' % (self.__class__.__name__, self.package_name,
- self.root_resource, id(self))
-
+from pyramid.response import Response
+from pyramid.traversal import traversal_path
+from pyramid.traversal import quote_path_segment
+
+def init_mimetypes(mimetypes):
+ # this is a function so it can be unittested
+ if hasattr(mimetypes, 'init'):
+ mimetypes.init()
+ return True
+ return False
+
+# See http://bugs.python.org/issue5853 which is a recursion bug
+# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
+# has been applied on the Python 2 trunk).
+init_mimetypes(mimetypes)
+
+class _FileResponse(Response):
+ """
+ Serves a static filelike object.
+ """
+ def __init__(self, path, cache_max_age):
+ super(_FileResponse, self).__init__(conditional_response=True)
+ self.last_modified = getmtime(path)
+ content_type = mimetypes.guess_type(path, strict=False)[0]
+ if content_type is None:
+ content_type = 'application/octet-stream'
+ self.content_type = content_type
+ content_length = getsize(path)
+ self.app_iter = _FileIter(open(path, 'rb'), content_length)
+ # assignment of content_length must come after assignment of app_iter
+ self.content_length = content_length
+ if cache_max_age is not None:
+ self.cache_expires = cache_max_age
+
+class _FileIter(object):
+ block_size = 4096 * 64 # (256K)
+
+ def __init__(self, file, size=None):
+ self.file = file
+ self.size = size
+
+ def __iter__(self):
+ return self
+
+ def next(self):
+ chunk_size = self.block_size
+ if self.size is not None:
+ if chunk_size > self.size:
+ chunk_size = self.size
+ self.size -= chunk_size
+ data = self.file.read(chunk_size)
+ if not data:
+ raise StopIteration
+ return data
+
+ def close(self):
+ self.file.close()
class static_view(object):
""" An instance of this class is a callable which can act as a
@@ -120,24 +108,73 @@ class static_view(object):
package-relative directory. However, if the ``root_dir`` is
absolute, configuration will not be able to
override the assets it contains. """
-
+
def __init__(self, root_dir, cache_max_age=3600, package_name=None,
- use_subpath=False):
+ use_subpath=False, index='index.html'):
# package_name is for bw compat; it is preferred to pass in a
# package-relative path as root_dir
# (e.g. ``anotherpackage:foo/static``).
+ self.cache_max_age = cache_max_age
if package_name is None:
package_name = caller_package().__name__
- package_name, root_dir = resolve_asset_spec(root_dir, package_name)
- if package_name is None:
- app = StaticURLParser(root_dir, cache_max_age=cache_max_age)
- else:
- app = PackageURLParser(
- package_name, root_dir, cache_max_age=cache_max_age)
- self.app = app
+ package_name, docroot = resolve_asset_spec(root_dir, package_name)
self.use_subpath = use_subpath
+ self.package_name = package_name
+ self.docroot = docroot
+ self.norm_docroot = normcase(normpath(docroot))
+ self.index = index
def __call__(self, context, request):
if self.use_subpath:
- return call_app_with_subpath_as_path_info(request, self.app)
- return request.get_response(self.app)
+ path_tuple = request.subpath
+ else:
+ path_tuple = traversal_path(request.path_info)
+
+ path = _secure_path(path_tuple)
+
+ if path is None:
+ # belt-and-suspenders security; this should never be true
+ # unless someone screws up the traversal_path code
+ # (request.subpath is computed via traversal_path too)
+ return HTTPNotFound('Out of bounds: %s' % request.url)
+
+ if self.package_name: # package resource
+
+ resource_path ='%s/%s' % (self.docroot.rstrip('/'), path)
+ if resource_isdir(self.package_name, resource_path):
+ if not request.path_url.endswith('/'):
+ return self.add_slash_redirect(request)
+ resource_path = '%s/%s' % (resource_path.rstrip('/'),self.index)
+ if not resource_exists(self.package_name, resource_path):
+ return HTTPNotFound(request.url)
+ filepath = resource_filename(self.package_name, resource_path)
+
+ else: # filesystem file
+
+ # os.path.normpath converts / to \ on windows
+ filepath = normcase(normpath(join(self.norm_docroot, path)))
+ if isdir(filepath):
+ if not request.path_url.endswith('/'):
+ return self.add_slash_redirect(request)
+ filepath = join(filepath, self.index)
+ if not exists(filepath):
+ return HTTPNotFound(request.url)
+
+ return _FileResponse(filepath ,self.cache_max_age)
+
+ def add_slash_redirect(self, request):
+ url = request.path_url + '/'
+ qs = request.query_string
+ if qs:
+ url = url + '?' + qs
+ return HTTPMovedPermanently(url)
+
+@lru_cache(1000)
+def _secure_path(path_tuple):
+ if '' in path_tuple:
+ return None
+ for item in path_tuple:
+ for val in ['.', '/']:
+ if item.startswith(val):
+ return None
+ return '/'.join([quote_path_segment(x) for x in path_tuple])
diff --git a/pyramid/tests/pkgs/static_abspath/__init__.py b/pyramid/tests/pkgs/static_abspath/__init__.py
new file mode 100644
index 000000000..812cca467
--- /dev/null
+++ b/pyramid/tests/pkgs/static_abspath/__init__.py
@@ -0,0 +1,7 @@
+import os
+
+def includeme(config):
+ here = here = os.path.dirname(__file__)
+ fixtures = os.path.normpath(os.path.join(here, '..', '..', 'fixtures'))
+ config.add_static_view('/', fixtures)
+
diff --git a/pyramid/tests/pkgs/static_assetspec/__init__.py b/pyramid/tests/pkgs/static_assetspec/__init__.py
new file mode 100644
index 000000000..cd6195397
--- /dev/null
+++ b/pyramid/tests/pkgs/static_assetspec/__init__.py
@@ -0,0 +1,3 @@
+def includeme(config):
+ config.add_static_view('/', 'pyramid.tests:fixtures')
+
diff --git a/pyramid/tests/test_config/test_views.py b/pyramid/tests/test_config/test_views.py
index 872528c6c..484d49c2f 100644
--- a/pyramid/tests/test_config/test_views.py
+++ b/pyramid/tests/test_config/test_views.py
@@ -1415,19 +1415,19 @@ class TestViewsConfigurationMixin(unittest.TestCase):
def test_add_static_view_here_no_utility_registered(self):
from pyramid.renderers import null_renderer
from zope.interface import Interface
- from pyramid.static import PackageURLParser
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
config = self._makeOne(autocommit=True)
- config.add_static_view('static', 'files',
- renderer=null_renderer)
+ config.add_static_view('static', 'files', renderer=null_renderer)
request_type = self._getRouteRequestIface(config, 'static/')
self._assertRoute(config, 'static/', 'static/*subpath')
wrapped = config.registry.adapters.lookup(
(IViewClassifier, request_type, Interface), IView, name='')
- request = self._makeRequest(config)
+ from pyramid.request import Request
+ request = Request.blank('/static/minimal.pt')
+ request.subpath = ('minimal.pt', )
result = wrapped(None, request)
- self.assertEqual(result.__class__, PackageURLParser)
+ self.assertEqual(result.status, '200 OK')
def test_add_static_view_package_relative(self):
from pyramid.interfaces import IStaticURLInfo
@@ -3346,7 +3346,6 @@ class TestStaticURLInfo(unittest.TestCase):
self.assertEqual(config.route_args, ('view/', 'view/*subpath'))
self.assertEqual(config.view_kw['permission'], NO_PERMISSION_REQUIRED)
self.assertEqual(config.view_kw['view'].__class__, static_view)
- self.assertEqual(config.view_kw['view'].app.cache_max_age, 1)
def test_add_viewname_with_permission(self):
config = DummyConfig()
@@ -3416,10 +3415,6 @@ class DummyRequest:
self.environ = environ
self.params = {}
self.cookies = {}
- def copy(self):
- return self
- def get_response(self, app):
- return app
class DummyContext:
pass
diff --git a/pyramid/tests/test_integration.py b/pyramid/tests/test_integration.py
index 239db58ba..3e2a6e452 100644
--- a/pyramid/tests/test_integration.py
+++ b/pyramid/tests/test_integration.py
@@ -41,94 +41,14 @@ class WGSIAppPlusViewConfigTests(unittest.TestCase):
(IViewClassifier, IRequest, INothing), IView, name='')
self.assertEqual(view.__original_view__, wsgiapptest)
-here = os.path.dirname(__file__)
-staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=True)
-
-class TestStaticApp(unittest.TestCase):
- def test_basic(self):
- from webob import Request
- context = DummyContext()
- from StringIO import StringIO
- request = Request({'PATH_INFO':'',
- 'SCRIPT_NAME':'',
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'80',
- 'REQUEST_METHOD':'GET',
- 'wsgi.version':(1,0),
- 'wsgi.url_scheme':'http',
- 'wsgi.input':StringIO()})
- request.subpath = ('minimal.pt',)
- result = staticapp(context, request)
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(
- result.body.replace('\r', ''),
- open(os.path.join(here, 'fixtures/minimal.pt'), 'r').read())
-
- def test_file_in_subdir(self):
- from webob import Request
- context = DummyContext()
- from StringIO import StringIO
- request = Request({'PATH_INFO':'',
- 'SCRIPT_NAME':'',
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'80',
- 'REQUEST_METHOD':'GET',
- 'wsgi.version':(1,0),
- 'wsgi.url_scheme':'http',
- 'wsgi.input':StringIO()})
- request.subpath = ('static', 'index.html',)
- result = staticapp(context, request)
- self.assertEqual(result.status, '200 OK')
- self.assertEqual(
- result.body.replace('\r', ''),
- open(os.path.join(here, 'fixtures/static/index.html'), 'r').read())
-
- def test_redirect_to_subdir(self):
- from webob import Request
- context = DummyContext()
- from StringIO import StringIO
- request = Request({'PATH_INFO':'',
- 'SCRIPT_NAME':'',
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'80',
- 'REQUEST_METHOD':'GET',
- 'wsgi.version':(1,0),
- 'wsgi.url_scheme':'http',
- 'wsgi.input':StringIO()})
- request.subpath = ('static',)
- result = staticapp(context, request)
- self.assertEqual(result.status, '301 Moved Permanently')
- self.assertEqual(result.location, 'http://localhost/static/')
-
- def test_redirect_to_subdir_with_existing_script_name(self):
- from webob import Request
- context = DummyContext()
- from StringIO import StringIO
- request = Request({'PATH_INFO':'/static',
- 'SCRIPT_NAME':'/script_name',
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'80',
- 'REQUEST_METHOD':'GET',
- 'wsgi.version':(1,0),
- 'wsgi.url_scheme':'http',
- 'wsgi.input':StringIO()})
- request.subpath = ('static',)
- result = staticapp(context, request)
- self.assertEqual(result.status, '301 Moved Permanently')
- self.assertEqual(result.location,
- 'http://localhost/script_name/static/')
-
-
-class IntegrationBase(unittest.TestCase):
+class IntegrationBase(object):
root_factory = None
package = None
def setUp(self):
from pyramid.config import Configurator
config = Configurator(root_factory=self.root_factory,
package=self.package)
- config.begin()
config.include(self.package)
- config.commit()
app = config.make_wsgi_app()
from webtest import TestApp
self.testapp = TestApp(app)
@@ -137,7 +57,124 @@ class IntegrationBase(unittest.TestCase):
def tearDown(self):
self.config.end()
-class TestFixtureApp(IntegrationBase):
+here = os.path.dirname(__file__)
+
+class TestStaticAppBase(IntegrationBase):
+ def _assertBody(self, body, filename):
+ self.assertEqual(
+ body.replace('\r', ''),
+ open(filename, 'r').read()
+ )
+
+ def test_basic(self):
+ res = self.testapp.get('/minimal.pt', status=200)
+ self._assertBody(res.body, os.path.join(here, 'fixtures/minimal.pt'))
+
+ def test_not_modified(self):
+ self.testapp.extra_environ = {
+ 'HTTP_IF_MODIFIED_SINCE':httpdate(pow(2, 32)-1)}
+ res = self.testapp.get('/minimal.pt', status=304)
+ self.assertEqual(res.body, '')
+
+ def test_file_in_subdir(self):
+ fn = os.path.join(here, 'fixtures/static/index.html')
+ res = self.testapp.get('/static/index.html', status=200)
+ self._assertBody(res.body, fn)
+
+ def test_directory_noslash_redir(self):
+ res = self.testapp.get('/static', status=301)
+ self.assertEqual(res.headers['Location'], 'http://localhost/static/')
+
+ def test_directory_noslash_redir_preserves_qs(self):
+ res = self.testapp.get('/static?a=1&b=2', status=301)
+ self.assertEqual(res.headers['Location'],
+ 'http://localhost/static/?a=1&b=2')
+
+ def test_directory_noslash_redir_with_scriptname(self):
+ self.testapp.extra_environ = {'SCRIPT_NAME':'/script_name'}
+ res = self.testapp.get('/static', status=301)
+ self.assertEqual(res.headers['Location'],
+ 'http://localhost/script_name/static/')
+
+ def test_directory_withslash(self):
+ fn = os.path.join(here, 'fixtures/static/index.html')
+ res = self.testapp.get('/static/', status=200)
+ self._assertBody(res.body, fn)
+
+ def test_range_inclusive(self):
+ self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1-2'}
+ res = self.testapp.get('/static/index.html', status=206)
+ self.assertEqual(res.body, 'ht')
+
+ def test_range_tilend(self):
+ self.testapp.extra_environ = {'HTTP_RANGE':'bytes=-5'}
+ res = self.testapp.get('/static/index.html', status=206)
+ self.assertEqual(res.body, 'tml>\n')
+
+ def test_range_notbytes(self):
+ self.testapp.extra_environ = {'HTTP_RANGE':'kHz=-5'}
+ res = self.testapp.get('/static/index.html', status=200)
+ self._assertBody(res.body,
+ os.path.join(here, 'fixtures/static/index.html'))
+
+ def test_range_multiple(self):
+ res = self.testapp.get('/static/index.html',
+ [('HTTP_RANGE', 'bytes=10-11,11-12')],
+ status=200)
+ self._assertBody(res.body,
+ os.path.join(here, 'fixtures/static/index.html'))
+
+ def test_range_oob(self):
+ self.testapp.extra_environ = {'HTTP_RANGE':'bytes=1000-1002'}
+ self.testapp.get('/static/index.html', status=416)
+
+ def test_notfound(self):
+ self.testapp.get('/static/wontbefound.html', status=404)
+
+ def test_oob_doubledot(self):
+ self.testapp.get('/static/../../test_integration.py', status=404)
+
+ def test_oob_slash(self):
+ self.testapp.get('/%2F/test_integration.py', status=404)
+ # XXX pdb this
+
+class TestStaticAppUsingAbsPath(TestStaticAppBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.static_abspath'
+
+class TestStaticAppUsingAssetSpec(TestStaticAppBase, unittest.TestCase):
+ package = 'pyramid.tests.pkgs.static_assetspec'
+
+class TestStaticAppNoSubpath(unittest.TestCase):
+ staticapp = static_view(os.path.join(here, 'fixtures'), use_subpath=False)
+ def _makeRequest(self, extra):
+ from pyramid.request import Request
+ from StringIO import StringIO
+ kw = {'PATH_INFO':'',
+ 'SCRIPT_NAME':'',
+ 'SERVER_NAME':'localhost',
+ 'SERVER_PORT':'80',
+ 'REQUEST_METHOD':'GET',
+ 'wsgi.version':(1,0),
+ 'wsgi.url_scheme':'http',
+ 'wsgi.input':StringIO()}
+ kw.update(extra)
+ request = Request(kw)
+ return request
+
+ def _assertBody(self, body, filename):
+ self.assertEqual(
+ body.replace('\r', ''),
+ open(filename, 'r').read()
+ )
+
+ def test_basic(self):
+ request = self._makeRequest({'PATH_INFO':'/minimal.pt'})
+ context = DummyContext()
+ result = self.staticapp(context, request)
+ self.assertEqual(result.status, '200 OK')
+ self._assertBody(result.body, os.path.join(here, 'fixtures/minimal.pt'))
+
+class TestFixtureApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.fixtureapp'
def test_another(self):
res = self.testapp.get('/another.html', status=200)
@@ -157,7 +194,7 @@ class TestFixtureApp(IntegrationBase):
def test_protected(self):
self.testapp.get('/protected.html', status=403)
-class TestStaticPermApp(IntegrationBase):
+class TestStaticPermApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.staticpermapp'
root_factory = 'pyramid.tests.pkgs.staticpermapp:RootFactory'
def test_allowed(self):
@@ -188,7 +225,7 @@ class TestStaticPermApp(IntegrationBase):
result.body.replace('\r', ''),
open(os.path.join(here, 'fixtures/static/index.html'), 'r').read())
-class TestCCBug(IntegrationBase):
+class TestCCBug(IntegrationBase, unittest.TestCase):
# "unordered" as reported in IRC by author of
# http://labs.creativecommons.org/2010/01/13/cc-engine-and-web-non-frameworks/
package = 'pyramid.tests.pkgs.ccbugapp'
@@ -200,7 +237,7 @@ class TestCCBug(IntegrationBase):
res = self.testapp.get('/licenses/1/v1/juri', status=200)
self.assertEqual(res.body, 'juri')
-class TestHybridApp(IntegrationBase):
+class TestHybridApp(IntegrationBase, unittest.TestCase):
# make sure views registered for a route "win" over views registered
# without one, even though the context of the non-route view may
# be more specific than the route view.
@@ -243,14 +280,14 @@ class TestHybridApp(IntegrationBase):
res = self.testapp.get('/error_sub', status=200)
self.assertEqual(res.body, 'supressed2')
-class TestRestBugApp(IntegrationBase):
+class TestRestBugApp(IntegrationBase, unittest.TestCase):
# test bug reported by delijati 2010/2/3 (http://pastebin.com/d4cc15515)
package = 'pyramid.tests.pkgs.restbugapp'
def test_it(self):
res = self.testapp.get('/pet', status=200)
self.assertEqual(res.body, 'gotten')
-class TestForbiddenAppHasResult(IntegrationBase):
+class TestForbiddenAppHasResult(IntegrationBase, unittest.TestCase):
# test that forbidden exception has ACLDenied result attached
package = 'pyramid.tests.pkgs.forbiddenapp'
def test_it(self):
@@ -265,7 +302,7 @@ class TestForbiddenAppHasResult(IntegrationBase):
self.assertTrue(
result.endswith("for principals ['system.Everyone']"))
-class TestViewDecoratorApp(IntegrationBase):
+class TestViewDecoratorApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.viewdecoratorapp'
def _configure_mako(self):
tmpldir = os.path.join(os.path.dirname(__file__),
@@ -286,7 +323,7 @@ class TestViewDecoratorApp(IntegrationBase):
res = self.testapp.get('/second', status=200)
self.assertTrue('OK2' in res.body)
-class TestViewPermissionBug(IntegrationBase):
+class TestViewPermissionBug(IntegrationBase, unittest.TestCase):
# view_execution_permitted bug as reported by Shane at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003603.html
package = 'pyramid.tests.pkgs.permbugapp'
def test_test(self):
@@ -296,7 +333,7 @@ class TestViewPermissionBug(IntegrationBase):
def test_x(self):
self.testapp.get('/x', status=403)
-class TestDefaultViewPermissionBug(IntegrationBase):
+class TestDefaultViewPermissionBug(IntegrationBase, unittest.TestCase):
# default_view_permission bug as reported by Wiggy at http://lists.repoze.org/pipermail/repoze-dev/2010-October/003602.html
package = 'pyramid.tests.pkgs.defpermbugapp'
def test_x(self):
@@ -316,7 +353,7 @@ from pyramid.tests.pkgs.exceptionviewapp.models import \
excroot = {'anexception':AnException(),
'notanexception':NotAnException()}
-class TestExceptionViewsApp(IntegrationBase):
+class TestExceptionViewsApp(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.exceptionviewapp'
root_factory = lambda *arg: excroot
def test_root(self):
@@ -475,7 +512,7 @@ class WSGIApp2AppTest(unittest.TestCase):
self.assertTrue('Hello' in res.body)
if os.name != 'java': # uses chameleon
- class RendererScanAppTest(IntegrationBase):
+ class RendererScanAppTest(IntegrationBase, unittest.TestCase):
package = 'pyramid.tests.pkgs.rendererscanapp'
def test_root(self):
res = self.testapp.get('/one', status=200)
@@ -505,3 +542,7 @@ class DummyRequest:
def get_response(self, application):
return application(None, None)
+def httpdate(ts):
+ import datetime
+ ts = datetime.datetime.utcfromtimestamp(ts)
+ return ts.strftime("%a, %d %b %Y %H:%M:%S GMT")
diff --git a/pyramid/tests/test_static.py b/pyramid/tests/test_static.py
index 357ec7551..81f25b95d 100644
--- a/pyramid/tests/test_static.py
+++ b/pyramid/tests/test_static.py
@@ -1,16 +1,16 @@
import unittest
-from pyramid.testing import cleanUp
+import datetime
-class TestPackageURLParser(unittest.TestCase):
+class Test_static_view_use_subpath_False(unittest.TestCase):
def _getTargetClass(self):
- from pyramid.static import PackageURLParser
- return PackageURLParser
+ from pyramid.static import static_view
+ return static_view
def _makeOne(self, *arg, **kw):
return self._getTargetClass()(*arg, **kw)
-
- def _makeEnviron(self, **kw):
+ def _makeRequest(self, kw=None):
+ from pyramid.request import Request
environ = {
'wsgi.url_scheme':'http',
'wsgi.version':(1,0),
@@ -20,332 +20,263 @@ class TestPackageURLParser(unittest.TestCase):
'SCRIPT_NAME':'',
'REQUEST_METHOD':'GET',
}
- environ.update(kw)
- return environ
+ if kw is not None:
+ environ.update(kw)
+ return Request(environ=environ)
- def test_ctor_allargs(self):
- import os.path
- inst = self._makeOne('package', 'resource/name', root_resource='root',
- cache_max_age=100)
- self.assertEqual(inst.package_name, 'package')
- self.assertEqual(inst.resource_name, os.path.join('resource', 'name'))
- self.assertEqual(inst.root_resource, 'root')
- self.assertEqual(inst.cache_max_age, 100)
-
def test_ctor_defaultargs(self):
- import os.path
- inst = self._makeOne('package', 'resource/name')
+ inst = self._makeOne('package:resource_name')
self.assertEqual(inst.package_name, 'package')
- self.assertEqual(inst.resource_name, os.path.join('resource', 'name'))
- self.assertEqual(inst.root_resource, os.path.join('resource', 'name'))
- self.assertEqual(inst.cache_max_age, None)
+ self.assertEqual(inst.docroot, 'resource_name')
+ self.assertEqual(inst.cache_max_age, 3600)
+ self.assertEqual(inst.index, 'index.html')
def test_call_adds_slash_path_info_empty(self):
- environ = self._makeEnviron(PATH_INFO='')
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('301 Moved Permanently' in body)
- self.assertTrue('http://example.com:6543/' in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':''})
+ context = DummyContext()
+ response = inst(context, request)
+ response.prepare(request.environ)
+ self.assertEqual(response.status, '301 Moved Permanently')
+ self.assertTrue('http://example.com:6543/' in response.body)
def test_path_info_slash_means_index_html(self):
- environ = self._makeEnviron()
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>static</html>' in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
def test_resource_out_of_bounds(self):
- environ = self._makeEnviron()
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- inst.root_resource = 'abcdef'
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('404 Not Found' in body)
- self.assertTrue('http://example.com:6543/' in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/subdir/../../minimal.pt'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
def test_resource_doesnt_exist(self):
- environ = self._makeEnviron(PATH_INFO='/notthere')
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('404 Not Found' in body)
- self.assertTrue('http://example.com:6543/' in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/notthere'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
def test_resource_isdir(self):
- environ = self._makeEnviron(PATH_INFO='/subdir/')
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>subdir</html>' in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/subdir/'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>subdir</html>' in response.body)
def test_resource_is_file(self):
- environ = self._makeEnviron(PATH_INFO='/index.html')
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>static</html>' in body)
-
- def test_resource_has_extra_path_info(self):
- environ = self._makeEnviron(PATH_INFO='/static/index.html/more')
- inst = self._makeOne('pyramid.tests', 'fixtures')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue("The trailing path '/more' is not allowed" in body)
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/index.html'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
def test_resource_is_file_with_cache_max_age(self):
- environ = self._makeEnviron(PATH_INFO='/index.html')
- inst = self._makeOne('pyramid.tests', 'fixtures/static',
- cache_max_age=600)
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>static</html>' in body)
- self.assertEqual(len(sr.headerlist), 8)
- header_names = [ x[0] for x in sr.headerlist ]
+ inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600)
+ request = self._makeRequest({'PATH_INFO':'/index.html'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
+ self.assertEqual(len(response.headerlist), 5)
+ header_names = [ x[0] for x in response.headerlist ]
header_names.sort()
self.assertEqual(header_names,
- ['Accept-Ranges', 'Cache-Control',
- 'Content-Length', 'Content-Range',
- 'Content-Type', 'ETag', 'Expires', 'Last-Modified'])
+ ['Cache-Control', 'Content-Length', 'Content-Type',
+ 'Expires', 'Last-Modified'])
def test_resource_is_file_with_no_cache_max_age(self):
- environ = self._makeEnviron(PATH_INFO='/index.html')
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>static</html>' in body)
- self.assertEqual(len(sr.headerlist), 6)
- header_names = [ x[0] for x in sr.headerlist ]
+ inst = self._makeOne('pyramid.tests:fixtures/static',
+ cache_max_age=None)
+ request = self._makeRequest({'PATH_INFO':'/index.html'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
+ self.assertEqual(len(response.headerlist), 3)
+ header_names = [ x[0] for x in response.headerlist ]
header_names.sort()
- self.assertEqual(header_names,
- ['Accept-Ranges', 'Content-Length', 'Content-Range',
- 'Content-Type', 'ETag', 'Last-Modified'])
-
- def test_with_root_resource(self):
- environ = self._makeEnviron(PATH_INFO='/static/index.html')
- inst = self._makeOne('pyramid.tests', 'fixtures',
- root_resource='fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- body = response[0]
- self.assertTrue('<html>static</html>' in body)
-
- def test_if_none_match(self):
- class DummyEq(object):
- def __eq__(self, other):
- return True
- dummy_eq = DummyEq()
- environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq)
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- response = inst(environ, sr)
- self.assertEqual(len(sr.headerlist), 1)
- self.assertEqual(sr.status, '304 Not Modified')
- self.assertEqual(sr.headerlist[0][0], 'ETag')
- self.assertEqual(response[0], '')
-
- def test_if_none_match_miss(self):
- class DummyEq(object):
- def __eq__(self, other):
- return False
- dummy_eq = DummyEq()
- environ = self._makeEnviron(HTTP_IF_NONE_MATCH=dummy_eq)
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- sr = DummyStartResponse()
- inst(environ, sr)
- self.assertEqual(len(sr.headerlist), 6)
- self.assertEqual(sr.status, '200 OK')
-
- def test_repr(self):
- import os.path
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- self.assertTrue(
- repr(inst).startswith(
- '<PackageURLParser pyramid.tests:%s at'
- % os.path.join('fixtures', 'static')))
+ self.assertEqual(
+ header_names,
+ ['Content-Length', 'Content-Type', 'Last-Modified'])
+
+ def test_resource_notmodified(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/index.html'})
+ request.if_modified_since = pow(2, 32) -1
+ context = DummyContext()
+ response = inst(context, request)
+ start_response = DummyStartResponse()
+ app_iter = response(request.environ, start_response)
+ self.assertEqual(start_response.status, '304 Not Modified')
+ self.assertEqual(list(app_iter), [])
def test_not_found(self):
- inst = self._makeOne('pyramid.tests', 'fixtures/static')
- environ = self._makeEnviron()
- sr = DummyStartResponse()
- response = inst.not_found(environ, sr, 'debug_message')
- body = response[0]
- self.assertTrue('404 Not Found' in body)
- self.assertEqual(sr.status, '404 Not Found')
-
-class Test_static_view(unittest.TestCase):
- def setUp(self):
- cleanUp()
-
- def tearDown(self):
- cleanUp()
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':'/notthere.html'})
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
+class Test_static_view_use_subpath_True(unittest.TestCase):
def _getTargetClass(self):
from pyramid.static import static_view
return static_view
- def _makeOne(self, path, package_name=None, use_subpath=False):
- return self._getTargetClass()(path, package_name=package_name,
- use_subpath=use_subpath)
-
- def _makeEnviron(self, **extras):
+ def _makeOne(self, *arg, **kw):
+ kw['use_subpath'] = True
+ return self._getTargetClass()(*arg, **kw)
+
+ def _makeRequest(self, kw=None):
+ from pyramid.request import Request
environ = {
'wsgi.url_scheme':'http',
'wsgi.version':(1,0),
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'8080',
+ 'SERVER_NAME':'example.com',
+ 'SERVER_PORT':'6543',
+ 'PATH_INFO':'/',
+ 'SCRIPT_NAME':'',
'REQUEST_METHOD':'GET',
}
- environ.update(extras)
- return environ
+ if kw is not None:
+ environ.update(kw)
+ return Request(environ=environ)
+
+ def test_ctor_defaultargs(self):
+ inst = self._makeOne('package:resource_name')
+ self.assertEqual(inst.package_name, 'package')
+ self.assertEqual(inst.docroot, 'resource_name')
+ self.assertEqual(inst.cache_max_age, 3600)
+ self.assertEqual(inst.index, 'index.html')
- def test_abspath_subpath(self):
- import os.path
- path = os.path.dirname(__file__)
- view = self._makeOne(path, use_subpath=True)
+ def test_call_adds_slash_path_info_empty(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest({'PATH_INFO':''})
+ request.subpath = ()
context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(response.directory, os.path.normcase(path))
-
- def test_relpath_subpath(self):
- path = 'fixtures'
- view = self._makeOne(path, use_subpath=True)
+ response = inst(context, request)
+ response.prepare(request.environ)
+ self.assertEqual(response.status, '301 Moved Permanently')
+ self.assertTrue('http://example.com:6543/' in response.body)
+
+ def test_path_info_slash_means_index_html(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ()
context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(response.root_resource, 'fixtures')
- self.assertEqual(response.resource_name, 'fixtures')
- self.assertEqual(response.package_name, 'pyramid.tests')
- self.assertEqual(response.cache_max_age, 3600)
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
- def test_relpath_notsubpath(self):
- path = 'fixtures'
- view = self._makeOne(path)
+ def test_resource_out_of_bounds(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ('subdir', '..', '..', 'minimal.pt')
context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertTrue(not hasattr(request, 'copied'))
- self.assertEqual(response.root_resource, 'fixtures')
- self.assertEqual(response.resource_name, 'fixtures')
- self.assertEqual(response.package_name, 'pyramid.tests')
- self.assertEqual(response.cache_max_age, 3600)
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
- def test_relpath_withpackage_subpath(self):
- view = self._makeOne('another:fixtures', use_subpath=True)
+ def test_resource_doesnt_exist(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ('notthere,')
context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(response.root_resource, 'fixtures')
- self.assertEqual(response.resource_name, 'fixtures')
- self.assertEqual(response.package_name, 'another')
- self.assertEqual(response.cache_max_age, 3600)
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
- def test_relpath_withpackage_name_subpath(self):
- view = self._makeOne('fixtures', package_name='another',
- use_subpath=True)
+ def test_resource_isdir(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ('subdir',)
context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(response.root_resource, 'fixtures')
- self.assertEqual(response.resource_name, 'fixtures')
- self.assertEqual(response.package_name, 'another')
- self.assertEqual(response.cache_max_age, 3600)
+ response = inst(context, request)
+ self.assertTrue('<html>subdir</html>' in response.body)
- def test_no_subpath_preserves_path_info_and_script_name_subpath(self):
- view = self._makeOne('fixtures', package_name='another',
- use_subpath=True)
+ def test_resource_is_file(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ('index.html',)
context = DummyContext()
- request = DummyRequest()
- request.subpath = ()
- request.environ = self._makeEnviron(PATH_INFO='/path_info',
- SCRIPT_NAME='/script_name')
- view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(request.environ['PATH_INFO'], '/')
- self.assertEqual(request.environ['SCRIPT_NAME'],
- '/script_name/path_info')
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
- def test_with_subpath_path_info_ends_with_slash_subpath(self):
- view = self._makeOne('fixtures', package_name='another',
- use_subpath=True)
+ def test_resource_is_file_with_cache_max_age(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static', cache_max_age=600)
+ request = self._makeRequest()
+ request.subpath = ('index.html',)
context = DummyContext()
- request = DummyRequest()
- request.subpath = ('subpath',)
- request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/')
- view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(request.environ['PATH_INFO'], '/subpath/')
- self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info')
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
+ self.assertEqual(len(response.headerlist), 5)
+ header_names = [ x[0] for x in response.headerlist ]
+ header_names.sort()
+ self.assertEqual(header_names,
+ ['Cache-Control', 'Content-Length', 'Content-Type',
+ 'Expires', 'Last-Modified'])
- def test_with_subpath_original_script_name_preserved(self):
- view = self._makeOne('fixtures', package_name='another',
- use_subpath=True)
+ def test_resource_is_file_with_no_cache_max_age(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static',
+ cache_max_age=None)
+ request = self._makeRequest()
+ request.subpath = ('index.html',)
+ context = DummyContext()
+ response = inst(context, request)
+ self.assertTrue('<html>static</html>' in response.body)
+ self.assertEqual(len(response.headerlist), 3)
+ header_names = [ x[0] for x in response.headerlist ]
+ header_names.sort()
+ self.assertEqual(
+ header_names,
+ ['Content-Length', 'Content-Type', 'Last-Modified'])
+
+ def test_resource_notmodified(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.if_modified_since = pow(2, 32) -1
+ request.subpath = ('index.html',)
context = DummyContext()
- request = DummyRequest()
- request.subpath = ('subpath',)
- request.environ = self._makeEnviron(PATH_INFO='/path_info/subpath/',
- SCRIPT_NAME='/scriptname')
- view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(request.environ['PATH_INFO'], '/subpath/')
- self.assertEqual(request.environ['SCRIPT_NAME'],
- '/scriptname/path_info')
+ response = inst(context, request)
+ start_response = DummyStartResponse()
+ app_iter = response(request.environ, start_response)
+ self.assertEqual(start_response.status, '304 Not Modified')
+ self.assertEqual(list(app_iter), [])
- def test_with_subpath_new_script_name_fixes_trailing_slashes(self):
- view = self._makeOne('fixtures', package_name='another',
- use_subpath=True)
+ def test_not_found(self):
+ inst = self._makeOne('pyramid.tests:fixtures/static')
+ request = self._makeRequest()
+ request.subpath = ('notthere.html',)
context = DummyContext()
- request = DummyRequest()
- request.subpath = ('sub', 'path')
- request.environ = self._makeEnviron(PATH_INFO='/path_info//sub//path//')
- view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(request.environ['PATH_INFO'], '/sub/path/')
- self.assertEqual(request.environ['SCRIPT_NAME'], '/path_info')
+ response = inst(context, request)
+ self.assertEqual(response.status, '404 Not Found')
+
+class Test_patch_mimetypes(unittest.TestCase):
+ def _callFUT(self, module):
+ from pyramid.static import init_mimetypes
+ return init_mimetypes(module)
+
+ def test_has_init(self):
+ class DummyMimetypes(object):
+ def init(self):
+ self.initted = True
+ module = DummyMimetypes()
+ result = self._callFUT(module)
+ self.assertEqual(result, True)
+ self.assertEqual(module.initted, True)
+
+ def test_missing_init(self):
+ class DummyMimetypes(object):
+ pass
+ module = DummyMimetypes()
+ result = self._callFUT(module)
+ self.assertEqual(result, False)
-class DummyStartResponse:
- def __call__(self, status, headerlist, exc_info=None):
- self.status = status
- self.headerlist = headerlist
- self.exc_info = exc_info
-
class DummyContext:
pass
-class DummyRequest:
- def __init__(self, environ=None):
- if environ is None:
- environ = {}
- self.environ = environ
-
- def get_response(self, application):
- return application
-
- def copy(self):
- self.copied = True
- return self
-
+class DummyStartResponse:
+ status = ()
+ headers = ()
+ def __call__(self, status, headers):
+ self.status = status
+ self.headers = headers
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index 0ead63703..95caf21be 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -19,6 +19,9 @@ class TraversalPathTests(unittest.TestCase):
def test_twodots(self):
self.assertEqual(self._callFUT('foo/../bar'), (u'bar',))
+ def test_twodots_at_start(self):
+ self.assertEqual(self._callFUT('../../bar'), (u'bar',))
+
def test_element_urllquoted(self):
self.assertEqual(self._callFUT('/foo/space%20thing/bar'),
(u'foo', u'space thing', u'bar'))
diff --git a/pyramid/tests/test_view.py b/pyramid/tests/test_view.py
index 434bbb9ce..7f66a7563 100644
--- a/pyramid/tests/test_view.py
+++ b/pyramid/tests/test_view.py
@@ -548,27 +548,6 @@ class Test_default_exceptionresponse_view(unittest.TestCase):
result = self._callFUT(context, request)
self.assertEqual(result, 'abc')
-class Test_patch_mimetypes(unittest.TestCase):
- def _callFUT(self, module):
- from pyramid.view import init_mimetypes
- return init_mimetypes(module)
-
- def test_has_init(self):
- class DummyMimetypes(object):
- def init(self):
- self.initted = True
- module = DummyMimetypes()
- result = self._callFUT(module)
- self.assertEqual(result, True)
- self.assertEqual(module.initted, True)
-
- def test_missing_init(self):
- class DummyMimetypes(object):
- pass
- module = DummyMimetypes()
- result = self._callFUT(module)
- self.assertEqual(result, False)
-
class Test_static(unittest.TestCase):
def setUp(self):
from zope.deprecation import __show__
@@ -578,38 +557,14 @@ class Test_static(unittest.TestCase):
from zope.deprecation import __show__
__show__.on()
- def _getTargetClass(self):
+ def _makeOne(self, path, package_name):
from pyramid.view import static
- return static
-
- def _makeOne(self, path, package_name=None):
- return self._getTargetClass()(path, package_name=package_name)
+ return static(path, package_name)
- def _makeEnviron(self, **extras):
- environ = {
- 'wsgi.url_scheme':'http',
- 'wsgi.version':(1,0),
- 'SERVER_NAME':'localhost',
- 'SERVER_PORT':'8080',
- 'REQUEST_METHOD':'GET',
- }
- environ.update(extras)
- return environ
-
-
- def test_relpath_subpath(self):
+ def test_it(self):
path = 'fixtures'
- view = self._makeOne(path)
- context = DummyContext()
- request = DummyRequest()
- request.subpath = ['__init__.py']
- request.environ = self._makeEnviron()
- response = view(context, request)
- self.assertEqual(request.copied, True)
- self.assertEqual(response.root_resource, 'fixtures')
- self.assertEqual(response.resource_name, 'fixtures')
- self.assertEqual(response.package_name, 'pyramid.tests')
- self.assertEqual(response.cache_max_age, 3600)
+ view = self._makeOne(path, None)
+ self.assertEqual(view.docroot, 'fixtures')
class ExceptionResponse(Exception):
status = '404 Not Found'
@@ -632,13 +587,6 @@ class DummyRequest:
environ = {}
self.environ = environ
- def get_response(self, application):
- return application
-
- def copy(self):
- self.copied = True
- return self
-
from pyramid.interfaces import IResponse
from zope.interface import implements
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index 2004fcad2..4beb27af3 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -479,7 +479,8 @@ def traversal_path(path):
if not segment or segment=='.':
continue
elif segment == '..':
- del clean[-1]
+ if clean:
+ del clean[-1]
else:
try:
segment = segment.decode('utf-8')
diff --git a/pyramid/view.py b/pyramid/view.py
index 96b7c6413..8b8ac58ce 100644
--- a/pyramid/view.py
+++ b/pyramid/view.py
@@ -1,4 +1,3 @@
-import mimetypes
import venusian
from zope.interface import providedBy
@@ -14,21 +13,6 @@ from pyramid.path import caller_package
from pyramid.static import static_view
from pyramid.threadlocal import get_current_registry
-def init_mimetypes(mimetypes):
- # this is a function so it can be unittested
- if hasattr(mimetypes, 'init'):
- mimetypes.init()
- return True
- return False
-
-# See http://bugs.python.org/issue5853 which is a recursion bug
-# that seems to effect Python 2.6, Python 2.6.1, and 2.6.2 (a fix
-# has been applied on the Python 2 trunk). This workaround should
-# really be in Paste if anywhere, but it's easiest to just do it
-# here and get it over with to avoid needing to deal with any
-# fallout.
-init_mimetypes(mimetypes)
-
_marker = object()
class static(static_view):