Commit f33ea934 authored by Bryton Lacquement's avatar Bryton Lacquement 🚪 Committed by Julien Muchembled

patches: make the WSGIPublisher backport work with Zope 2.13

Parts of ZPublisher.utils are also backported.
parent 81c8663f
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
############################################################################## ##############################################################################
# Load all monkey patches # Load all monkey patches
from Products.ERP5Type.patches import WSGIPublisher
from Products.ERP5Type.patches import HTTPRequest from Products.ERP5Type.patches import HTTPRequest
from Products.ERP5Type.patches import AccessControl_patch from Products.ERP5Type.patches import AccessControl_patch
from Products.ERP5Type.patches import Restricted from Products.ERP5Type.patches import Restricted
......
# Backport from Zope4 # Backport (with modified code) from Zope4
############################################################################## ##############################################################################
# #
...@@ -19,16 +19,22 @@ from contextlib import closing ...@@ -19,16 +19,22 @@ from contextlib import closing
from contextlib import contextmanager from contextlib import contextmanager
from io import BytesIO from io import BytesIO
from io import IOBase from io import IOBase
import logging
from six import binary_type
from six import PY3 from six import PY3
from six import reraise from six import reraise
from six import text_type
from six.moves._thread import allocate_lock from six.moves._thread import allocate_lock
import transaction import transaction
from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import aq_acquire from Acquisition import aq_acquire
from Acquisition import aq_inner
from Acquisition import aq_parent
from transaction.interfaces import TransientError from transaction.interfaces import TransientError
from zExceptions import Redirect
from zExceptions import Unauthorized from zExceptions import Unauthorized
from zExceptions import upgradeException from zExceptions import upgradeException
from zope.component import queryMultiAdapter from zope.component import queryMultiAdapter
...@@ -38,12 +44,12 @@ from zope.globalrequest import setRequest ...@@ -38,12 +44,12 @@ from zope.globalrequest import setRequest
from zope.publisher.skinnable import setDefaultSkin from zope.publisher.skinnable import setDefaultSkin
from zope.security.management import endInteraction from zope.security.management import endInteraction
from zope.security.management import newInteraction from zope.security.management import newInteraction
from ZPublisher import pubevents from Zope2.App.startup import validated_hook
from ZPublisher.HTTPRequest import WSGIRequest from ZPublisher import pubevents, Retry
from ZPublisher.HTTPResponse import WSGIResponse from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.Iterators import IUnboundStreamIterator from ZPublisher.Iterators import IUnboundStreamIterator
from ZPublisher.mapply import mapply from ZPublisher.mapply import mapply
from ZPublisher.utils import recordMetaData from ZPublisher.WSGIPublisher import call_object, missing_name, WSGIResponse
if sys.version_info >= (3, ): if sys.version_info >= (3, ):
...@@ -57,32 +63,113 @@ _MODULE_LOCK = allocate_lock() ...@@ -57,32 +63,113 @@ _MODULE_LOCK = allocate_lock()
_MODULES = {} _MODULES = {}
def call_object(obj, args, request): AC_LOGGER = logging.getLogger('event.AccessControl')
return obj(*args)
if 1: # upstream moved WSGIResponse to HTTPResponse.py
def dont_publish_class(klass, request): # According to PEP 333, WSGI applications and middleware are forbidden from
request.response.forbiddenError("class %s" % klass.__name__) # using HTTP/1.1 "hop-by-hop" features or headers. This patch prevents Zope
# from sending 'Connection' and 'Transfer-Encoding' headers.
def finalize(self):
headers = self.headers
body = self.body
# <patch>
# There's a bug in 'App.ImageFile.index_html': when it returns a 304 status
# code, 'Content-Length' is equal to a nonzero value.
if self.status == 304:
headers.pop('content-length', None)
# Force the removal of "hop-by-hop" headers
headers.pop('Connection', None)
# </patch>
# set 204 (no content) status if 200 and response is empty
# and not streaming
if ('content-type' not in headers and
'content-length' not in headers and
not self._streaming and self.status == 200):
self.setStatus('nocontent')
# add content length if not streaming
content_length = headers.get('content-length')
if content_length is None and not self._streaming:
self.setHeader('content-length', len(body))
# <patch>
# backport from Zope 4.0b1
# (see commit be5b14bd858da787c41a39e2533b0aabcd246fd5)
# </patch>
return '%s %s' % (self.status, self.errmsg), self.listHeaders()
WSGIResponse.finalize = finalize
def missing_name(name, request):
if name == 'self':
return request['PARENTS'][0]
request.response.badRequestError(name)
# From ZPublisher.utils
def recordMetaData(object, request):
if hasattr(object, 'getPhysicalPath'):
path = '/'.join(object.getPhysicalPath())
else:
# Try hard to get the physical path of the object,
# but there are many circumstances where that's not possible.
to_append = ()
def validate_user(request, user): if hasattr(object, '__self__') and hasattr(object, '__name__'):
newSecurityManager(request, user) # object is a Python method.
to_append = (object.__name__,)
object = object.__self__
while object is not None and not hasattr(object, 'getPhysicalPath'):
if getattr(object, '__name__', None) is None:
object = None
break
to_append = (object.__name__,) + to_append
object = aq_parent(aq_inner(object))
if object is not None:
path = '/'.join(object.getPhysicalPath() + to_append)
else:
# As Jim would say, "Waaaaaaaa!"
# This may cause problems with virtual hosts
# since the physical path is different from the path
# used to retrieve the object.
path = request.get('PATH_INFO')
T = transaction.get()
T.note(safe_unicode(path))
auth_user = request.get('AUTHENTICATED_USER', None)
if auth_user:
auth_folder = aq_parent(auth_user)
if auth_folder is None:
AC_LOGGER.warning(
'A user object of type %s has no aq_parent.',
type(auth_user))
auth_path = request.get('AUTHENTICATION_PATH')
else:
auth_path = '/'.join(auth_folder.getPhysicalPath()[1:-1])
user_id = auth_user.getId()
user_id = safe_unicode(user_id) if user_id else u'None'
T.setUser(user_id, safe_unicode(auth_path))
def set_default_debug_mode(debug_mode): def safe_unicode(value):
global _DEFAULT_DEBUG_MODE if isinstance(value, text_type):
_DEFAULT_DEBUG_MODE = debug_mode return value
elif isinstance(value, binary_type):
try:
value = text_type(value, 'utf-8')
except UnicodeDecodeError:
value = value.decode('utf-8', 'replace')
return value
def set_default_authentication_realm(realm): def dont_publish_class(klass, request):
global _DEFAULT_REALM request.response.forbiddenError("class %s" % klass.__name__)
_DEFAULT_REALM = realm
def get_module_info(module_name='Zope2'): def get_module_info(module_name='Zope2'):
...@@ -95,7 +182,8 @@ def get_module_info(module_name='Zope2'): ...@@ -95,7 +182,8 @@ def get_module_info(module_name='Zope2'):
module = __import__(module_name) module = __import__(module_name)
app = getattr(module, 'bobo_application', module) app = getattr(module, 'bobo_application', module)
realm = _DEFAULT_REALM if _DEFAULT_REALM is not None else module_name realm = _DEFAULT_REALM if _DEFAULT_REALM is not None else module_name
_MODULES[module_name] = info = (app, realm, _DEFAULT_DEBUG_MODE) error_hook = getattr(module,'zpublisher_exception_hook', None)
_MODULES[module_name] = info = (app, realm, _DEFAULT_DEBUG_MODE, error_hook)
return info return info
...@@ -135,7 +223,7 @@ def _exc_view_created_response(exc, request, response): ...@@ -135,7 +223,7 @@ def _exc_view_created_response(exc, request, response):
@contextmanager @contextmanager
def transaction_pubevents(request, response, tm=transaction.manager): def transaction_pubevents(request, response, err_hook, tm=transaction.manager):
try: try:
setDefaultSkin(request) setDefaultSkin(request)
newInteraction() newInteraction()
...@@ -166,21 +254,44 @@ def transaction_pubevents(request, response, tm=transaction.manager): ...@@ -166,21 +254,44 @@ def transaction_pubevents(request, response, tm=transaction.manager):
if request.environ.get('x-wsgiorg.throw_errors', False): if request.environ.get('x-wsgiorg.throw_errors', False):
reraise(*exc_info) reraise(*exc_info)
# Handle exception view if err_hook:
exc_view_created = _exc_view_created_response( parents = request['PARENTS']
exc, request, response) if parents:
parents = parents[0]
if isinstance(exc, Unauthorized): retry = False
# _unauthorized modifies the response in-place. If this hook try:
# is used, an exception view for Unauthorized has to merge try:
# the state of the response and the exception instance. r = err_hook(parents, request, *exc_info)
exc.setRealm(response.realm) assert r is response
response._unauthorized() exc_view_created = True
response.setStatus(exc.getStatus()) except Retry:
if request.supports_retry():
retry = False retry = True
if isinstance(exc, TransientError) and request.supports_retry(): else:
retry = True r = err_hook(parents, request, *sys.exc_info())
assert r is response
exc_view_created = True
except (Redirect, Unauthorized):
response.exception()
exc_view_created = True
except BaseException as e:
if e is not exc:
raise
exc_view_created = False
else:
# Handle exception view
exc_view_created = _exc_view_created_response(
exc, request, response)
if isinstance(exc, Unauthorized):
# _unauthorized modifies the response in-place. If this hook
# is used, an exception view for Unauthorized has to merge
# the state of the response and the exception instance.
exc.setRealm(response.realm)
response._unauthorized()
response.setStatus(exc.getStatus())
retry = isinstance(exc, TransientError) and request.supports_retry()
notify(pubevents.PubBeforeAbort(request, exc_info, retry)) notify(pubevents.PubBeforeAbort(request, exc_info, retry))
tm.abort() tm.abort()
...@@ -217,7 +328,7 @@ def publish(request, module_info): ...@@ -217,7 +328,7 @@ def publish(request, module_info):
path = request.get('PATH_INFO') path = request.get('PATH_INFO')
request['PARENTS'] = [obj] request['PARENTS'] = [obj]
obj = request.traverse(path, validated_hook=validate_user) obj = request.traverse(path, validated_hook=validated_hook)
notify(pubevents.PubAfterTraversal(request)) notify(pubevents.PubAfterTraversal(request))
recordMetaData(obj, request) recordMetaData(obj, request)
...@@ -245,7 +356,7 @@ def load_app(module_info): ...@@ -245,7 +356,7 @@ def load_app(module_info):
try: try:
yield (app, realm, debug_mode) yield (app, realm, debug_mode)
finally: finally:
if transaction.manager.manager._txn is not None: if transaction.manager._txn is not None:
# Only abort a transaction, if one exists. Otherwise the # Only abort a transaction, if one exists. Otherwise the
# abort creates a new transaction just to abort it. # abort creates a new transaction just to abort it.
transaction.abort() transaction.abort()
...@@ -257,9 +368,10 @@ def publish_module(environ, start_response, ...@@ -257,9 +368,10 @@ def publish_module(environ, start_response,
_response=None, _response=None,
_response_factory=WSGIResponse, _response_factory=WSGIResponse,
_request=None, _request=None,
_request_factory=WSGIRequest, _request_factory=HTTPRequest,
_module_name='Zope2'): _module_name='Zope2'):
module_info = get_module_info(_module_name) module_info = get_module_info(_module_name)
module_info, err_hook = module_info[:3], module_info[3]
result = () result = ()
path_info = environ.get('PATH_INFO') path_info = environ.get('PATH_INFO')
...@@ -294,7 +406,7 @@ def publish_module(environ, start_response, ...@@ -294,7 +406,7 @@ def publish_module(environ, start_response,
setRequest(request) setRequest(request)
try: try:
with load_app(module_info) as new_mod_info: with load_app(module_info) as new_mod_info:
with transaction_pubevents(request, response): with transaction_pubevents(request, response, err_hook):
response = _publish(request, new_mod_info) response = _publish(request, new_mod_info)
break break
except TransientError: except TransientError:
...@@ -324,3 +436,6 @@ def publish_module(environ, start_response, ...@@ -324,3 +436,6 @@ def publish_module(environ, start_response,
# Return the result body iterable. # Return the result body iterable.
return result return result
sys.modules['ZPublisher.WSGIPublisher'] = sys.modules[__name__]
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment