diff options
| author | Chris McDonough <chrism@plope.com> | 2011-09-06 21:19:08 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-09-06 21:19:08 -0400 |
| commit | dc98227171b49237d5236a2fbac0889b7cc07da4 (patch) | |
| tree | 5c6b31626b17b464c04b21bfb904bd2730100218 | |
| parent | f506adb6a9c5e4dd867c92cc9ae1edb558ec131d (diff) | |
| parent | 315f755fd8c04e90d01c14a97afae8778cf45f05 (diff) | |
| download | pyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.tar.gz pyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.tar.bz2 pyramid-dc98227171b49237d5236a2fbac0889b7cc07da4.zip | |
Merge branch 'feature.staticfilegeddon'
| -rw-r--r-- | CHANGES.txt | 14 | ||||
| -rw-r--r-- | TODO.txt | 9 | ||||
| -rw-r--r-- | pyramid/static.py | 209 | ||||
| -rw-r--r-- | pyramid/tests/pkgs/static_abspath/__init__.py | 7 | ||||
| -rw-r--r-- | pyramid/tests/pkgs/static_assetspec/__init__.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_views.py | 15 | ||||
| -rw-r--r-- | pyramid/tests/test_integration.py | 225 | ||||
| -rw-r--r-- | pyramid/tests/test_static.py | 491 | ||||
| -rw-r--r-- | pyramid/tests/test_traversal.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_view.py | 62 | ||||
| -rw-r--r-- | pyramid/traversal.py | 3 | ||||
| -rw-r--r-- | pyramid/view.py | 16 |
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) ================== @@ -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): |
