Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Accept header normalization #620

Merged
merged 3 commits into from

3 participants

@lmctv

According to RFC 2616, the mimetype in the accept header should be matched in a case insensitive way.

While fixing the root case insensitivity in webob (Pylons/webob#56), I noticed a glitch in view registration: if you run pyramid_view_accept.py with an unpatched pyramid, both the view callables get registered; which one will be called depends on the client-supplied Accept header, and the webob available version...

$ python pyramid_view_accept.py
Route: service, Path: /, Callable: service_HTML, Accept: text/HTML
Route: service, Path: /, Callable: service_html, Accept: text/html
Listening on port 6543
Accept: text/html
localhost.localdomain - - [08/Jun/2012 18:05:41] "GET / HTTP/1.1" 200 92
Accept: text/HTML
localhost.localdomain - - [08/Jun/2012 18:10:39] "GET / HTTP/1.0" 200 92

With the proposed patches, the configurator would notice the attempted shadowing and correctly raises a ConfigurationConflictError:

$ python pyramid_view_accept.py
Traceback (most recent call last):
  File "pyramid_view_accept.py", line 38, in <module>
    app = config.make_wsgi_app()
  File "/home/lorenzo/pyramid/pyramid/config/__init__.py", line 921, in make_wsgi_app
    self.commit()
  File "/home/lorenzo/pyramid/pyramid/config/__init__.py", line 594, in commit
    self.action_state.execute_actions(introspector=self.introspector)
  File "/home/lorenzo/pyramid/pyramid/config/__init__.py", line 1030, in execute_actions
    for action in resolveConflicts(self.actions):
  File "/home/lorenzo/pyramid/pyramid/config/__init__.py", line 1136, in resolveConflicts
    raise ConfigurationConflictError(conflicts)
pyramid.exceptions.ConfigurationConflictError: Conflicting configuration actions
  For: ('view', None, '', None, <InterfaceClass pyramid.interfaces.IView>, None, None, ('GET', 'HEAD'), 'service', None, False, 'text/html', None, None, None)
    Line 12 of file pyramid_view_accept.py:
        accept="text/HTML")
    Line 18 of file pyramid_view_accept.py:
        accept="text/html")

pyramid_view_accept.py:

#!python
# encoding: utf-8

from pyramid.config import Configurator
from wsgiref.simple_server import make_server
from pyramid.view import view_config
from pyramid.response import Response

import sys

@view_config(route_name='service', request_method='GET',
             accept="text/HTML")
def service_HTML(request):
    return Response('<html><head><title>HTML SERVICE</title></head><body><p>route_name=service</p></body></html>\n')

@view_config(route_name='service', request_method='GET',
             accept="text/html")
def service_html(request):
    return Response('<html><head><title>html service</title></head><body><p>route_name=service</p></body></html>\n')


if __name__ == '__main__':
    # configuration settings
    if len(sys.argv) > 1:
        httpport = int(sys.argv[1])
    else:
        httpport = 6543
    settings = {}
    settings['reload_all'] = True
    settings['debug_all'] = True
    # configuration setup
    config = Configurator(settings=settings)
    config.add_route(name='service', pattern='/')
    config.scan()
    # serve app
    app = config.make_wsgi_app()
    # print view mappings
    rt_map = config.get_routes_mapper()
    views = config.introspector.get_category('views')
    for view in views:
        _callable = view['introspectable']['callable'].__name__
        _accept = view['introspectable']['accept']
        _route_nm = view['introspectable']['route_name']
        _route = rt_map.get_route(_route_nm)
        if _route:
            print 'Route: %s, Path: %s, Callable: %s, Accept: %s' % (_route.name, _route.path, _callable, _accept)
    server = make_server('', httpport, app)
    print 'Listening on port %i' % httpport
    server.serve_forever()
lmctv added some commits
@lmctv lmctv RFC 2616 sec. 3.7 case insensitive match test
Since "... The type, subtype, and parameter attribute names are case-
insensitive..." the type and subtype in the accept parameter in add_view
should be treated in a case insensitive way.
34f00ae
@lmctv lmctv Lowercase the accept parameter in add_view
Fix the RFC 2616 sec. 3.7 compliance by storing a canonical cased
version of the parameter.
ed9663c
@mcdonc
Owner

This looks good. Do you think you can also put your name in CONTRIBUTORS.txt (in the Pyramid root project dir)?

@lmctv lmctv Accept the Contributor Agreement.
as suggested by "Contributing Source Code and Documentation" document.
ce5b5e4
@mmerickel mmerickel merged commit ce5b5e4 into from
@mmerickel
Owner

thanks, I fixed a py3 compatibility and updated changes.txt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 8, 2012
  1. @lmctv

    RFC 2616 sec. 3.7 case insensitive match test

    lmctv authored
    Since "... The type, subtype, and parameter attribute names are case-
    insensitive..." the type and subtype in the accept parameter in add_view
    should be treated in a case insensitive way.
  2. @lmctv

    Lowercase the accept parameter in add_view

    lmctv authored
    Fix the RFC 2616 sec. 3.7 compliance by storing a canonical cased
    version of the parameter.
Commits on Jun 13, 2012
  1. @lmctv

    Accept the Contributor Agreement.

    lmctv authored
    as suggested by "Contributing Source Code and Documentation" document.
This page is out of date. Refresh to see the latest.
View
2  CONTRIBUTORS.txt
@@ -172,3 +172,5 @@ Contributors
- Wayne Witzel III, 2012/03/27
- Marin Rukavina, 2012/05/03
+
+- Lorenzo M. Catucci, 2012/06/08
View
3  pyramid/config/views.py
@@ -1001,6 +1001,9 @@ def view(context, request):
# GET implies HEAD too
request_method = as_sorted_tuple(request_method + ('HEAD',))
+ if accept is not None:
+ accept = accept.lower()
+
order, predicates, phash = make_predicates(xhr=xhr,
request_method=request_method, path_info=path_info,
request_param=request_param, header=header, accept=accept,
View
20 pyramid/tests/test_config/test_views.py
@@ -645,6 +645,26 @@ def view2(context, request):
request.accept = DummyAccept('text/html', 'text/html')
self.assertEqual(wrapper(None, request), 'OK2')
+ def test_add_view_mixed_case_replaces_existing_view(self):
+ from pyramid.renderers import null_renderer
+ def view(context, request): return 'OK'
+ def view2(context, request): return 'OK2'
+ def view3(context, request): return 'OK3'
+ def get_val(obj, key):
+ return obj.get(key)
+ config = self._makeOne(autocommit=True)
+ config.add_view(view=view, renderer=null_renderer)
+ config.add_view(view=view2, accept='text/html', renderer=null_renderer)
+ config.add_view(view=view3, accept='text/HTML', renderer=null_renderer)
+ wrapper = self._getViewCallable(config)
+ self.assertTrue(IMultiView.providedBy(wrapper))
+ self.assertEqual(len(wrapper.media_views.items()),1)
+ self.assertFalse(wrapper.media_views.has_key('text/HTML'))
+ self.assertEqual(wrapper(None, None), 'OK')
+ request = DummyRequest()
+ request.accept = DummyAccept('text/html', 'text/html')
+ self.assertEqual(wrapper(None, request), 'OK3')
+
def test_add_views_with_accept_multiview_replaces_existing(self):
from pyramid.renderers import null_renderer
def view(context, request): return 'OK'
Something went wrong with that request. Please try again.