HEAD requests in presents of browser:defaultView gives AttributeError in publisher
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
Silva |
Fix Released
|
Medium
|
Unassigned | ||
Zope 2 |
Invalid
|
Low
|
Unassigned |
Bug Description
Let's define a default view for a normal Zope folder, like with the following ZCML:
<browser:page
name=
for=
template=
permission=
/>
<browser:
for=
name=
/>
When you now issue a HEAD request against a folder, like so:
curl -X HEAD --head http://
(it's important to issue this request against something else than the top folder, as the top folder does have correct behavior)
there will be an error in the ZPublisher:
Traceback (innermost last):
Module ZPublisher.Publish, line 110, in publish
Module ZPublisher.
AttributeError: has_key
in the following piece of code:
if (no_acquire_flag and
not hasattr(
if not (hasattr(
There'll be an AttributeError because parents[
parents[1].aq_base in this context is Folder. entry name will be @@index.html.
Apparently HEAD requests don't trigger the right code-path in this tortuous pile of ZPublisher code anymore.
This can be traced back to the browserDefault() method of DefaultPublishT
def browserDefault(
if hasattr(
return self.context.
# Zope 3.2 still uses IDefaultView name when it
# registeres default views, even though it's
# deprecated. So we handle that here:
if default_name is not None:
# Adding '@@' here forces this to be a view.
# A neater solution might be desireable.
return self.context, ('@@' + default_name,)
return self.context, ()
if default_name is not None, the failing behavior is triggered. The problem seems to go away if I change the method to this to bail out early if we're not dealing with GET or POST:
def browserDefault(
if hasattr(
return self.context.
if method not in ('GET', 'POST'):
return self.context, ()
# Zope 3.2 still uses IDefaultView name when it
# registeres default views, even though it's
# deprecated. So we handle that here:
if default_name is not None:
# Adding '@@' here forces this to be a view.
# A neater solution might be desireable.
return self.context, ('@@' + default_name,)
return self.context, ()
This may be a fix, but there may be a better fix...
Changed in zope2: | |
importance: | Undecided → Low |
status: | New → Confirmed |
Jasper and Martijn here. We've been reviewing this more in depth and have concluded
that the patched described above isn't the right solution. It still fails for HEAD requests
issued against a Five-view directly for instance, it just happens to work for default views.
We dug into this problem some more, and we think a better solution would be to add Five.BrowserVie w).
a HEAD method to Zope 2's BrowserView base class (Products.
This default HEAD method should then delegate the request to the content object, which
(in the Resource base class of SimpleItem) defines HEAD. People should also be allow
to override it in the view simply.
In order to define a working HEAD request we have to please Zope security, and we do this in the following hackish way, delegating it to the content-object security (approach thanks to Daniel Nouri):
from AccessControl. ZopeSecurityPol icy import getRoles
class MyRolesProxy:
def __init__(self, method):
self.method = method
def rolesForPermiss ionOn(self, value): self.aq_ inner.aq_ parent
getattr( context, self.method),
None)
context = value.im_
return getRoles(context, self.method,
then we define a HEAD method on the view which delegates everything back to
the context:
def HEAD(self, REQUEST, RESPONSE=None): or_default( )
getSecurityMan ager(). validate( None, context, 'HEAD', func) if RESPONSE is None:
"""Proxy HTTP HEAD requests.
Docstring needs to exist to please Zope security.
"""
# A bit hackish, but it works.
context = self.context_
func = context.HEAD
return func(REQUEST)
return func(REQUEST, RESPONSE)
Finally we make sure that this HEAD method also delegates security to the context:
HEAD.__roles__ = MyRolesProxy( 'HEAD')
We think we should be doing this for other HTTP methods too (HEAD, PUT, OPTIONS, etc, basically anything non GET/POST).
An other approach to accomplish the same result would be to modify the ZPublisher so that this delegation happens there if the relevant method cannot be found. To find how to adjust the ZPublisher to this effect is a challenge, however. :)