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 Chris McDonough Michael Merickel
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
Chris McDonough
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
Michael Merickel mmerickel merged commit ce5b5e4 into from
Michael Merickel
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.
2  CONTRIBUTORS.txt
View
@@ -172,3 +172,5 @@ Contributors
- Wayne Witzel III, 2012/03/27
- Marin Rukavina, 2012/05/03
+
+- Lorenzo M. Catucci, 2012/06/08
3  pyramid/config/views.py
View
@@ -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,
20 pyramid/tests/test_config/test_views.py
View
@@ -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.