Commit 92e81dd4 authored by Vincent Pelletier's avatar Vincent Pelletier

ERP5Type.patches.Publish: Patch higher in the call stack.

Also covers traversal (and a lot more).
parent c1deaaed
Pipeline #7840 running with stage
##############################################################################
# Copyright (c) 2019 Nexedi SA and Contributors. All Rights Reserved.
# Copyright (c) 2019-2020 Nexedi SA and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
# Vincent Pelletier <vincent@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
......@@ -33,7 +34,7 @@ from Products.TimerService.timerserver.TimerServer import TimerRequest
__all__ = (
'TimeoutReachedError', 'Deadline', 'getDeadline', 'getTimeLeft',
'wrap_call_object',
'getPublisherDeadlineValue',
)
class TimeoutReachedError(Exception):
......@@ -101,21 +102,10 @@ def getTimeLeft():
deadline = getattr(_site_local, 'deadline', None)
return None if deadline is None else max(deadline - time.time(), 0.000001)
def wrap_call_object(call_object):
def getPublisherDeadlineValue(request):
"""
Return argument wrapped so it is executed in a timeout context using
publisher_timeout.
call_object (callable (object, args, request) -> any)
call_object-like function, which should be executed under a deadline based
on publisher_timeout value at call-time.
Dedline will not be applied if request is a TimerRequest instance, as these
requests are not strictly published, and hence do not fall under control of
the same setting.
Return an instance of Deadline class suitable for publication.
"""
def deadlined_call_object(object, args, request):
with Deadline(
None if isinstance(request, TimerRequest) else publisher_timeout,
):
return call_object(object, args, request)
return deadlined_call_object
return Deadline(
None if isinstance(request, TimerRequest) else publisher_timeout,
)
##############################################################################
# Copyright (c) 2019 Nexedi SA and Contributors. All Rights Reserved.
# Kazuhiko <kazuhiko@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
from Products.ERP5Type.Timeout import wrap_call_object
from Products.ERP5Type.Timeout import getPublisherDeadlineValue
from ZPublisher import Publish
from ZPublisher.Publish import (
# Produced using:
# dis.dis(
# compile(open(<this_file>, 'r').read(), 'foo', 'exec').co_consts[
# <index of publish code object in co_consts>
# ]
# )
# and checking all uniques LOAD_GLOBAL names, excluding builtins and
# getPublisherDeadlineValue, and including publish parameter default
# values.
ISkinnable,
PubAfterTraversal,
PubBeforeAbort,
PubBeforeCommit,
PubFailure,
PubStart,
PubSuccess,
Redirect,
Retry,
call_object,
dont_publish_class,
endInteraction,
get_module_info,
mapply,
missing_name,
newInteraction,
notify,
publish,
setDefaultSkin,
sys,
urlparse,
)
def publish(request, module_name, after_list, debug=0,
# Optimize:
call_object=call_object,
missing_name=missing_name,
dont_publish_class=dont_publish_class,
mapply=mapply,
):
(bobo_before, bobo_after, object, realm, debug_mode, err_hook,
validated_hook, transactions_manager)= get_module_info(module_name)
parents=None
response=None
try:
with getPublisherDeadlineValue(request):
notify(PubStart(request))
# TODO pass request here once BaseRequest implements IParticipation
newInteraction()
request.processInputs()
request_get=request.get
response=request.response
# First check for "cancel" redirect:
if request_get('SUBMIT', '').strip().lower() == 'cancel':
cancel = request_get('CANCEL_ACTION', '')
if cancel:
# Relative URLs aren't part of the spec, but are accepted by
# some browsers.
for part, base in zip(urlparse(cancel)[:3],
urlparse(request['BASE1'])[:3]):
if not part:
continue
if not part.startswith(base):
cancel = ''
break
if cancel:
raise Redirect, cancel
after_list[0]=bobo_after
if debug_mode:
response.debug_mode=debug_mode
if realm and not request.get('REMOTE_USER',None):
response.realm=realm
if bobo_before is not None:
bobo_before()
# Get the path list.
# According to RFC1738 a trailing space in the path is valid.
path=request_get('PATH_INFO')
request['PARENTS']=parents=[object]
if transactions_manager:
transactions_manager.begin()
object=request.traverse(path, validated_hook=validated_hook)
notify(PubAfterTraversal(request))
if transactions_manager:
transactions_manager.recordMetaData(object, request)
result=mapply(object, request.args, request,
call_object,1,
missing_name,
dont_publish_class,
request, bind=1)
if result is not response:
response.setBody(result)
notify(PubBeforeCommit(request))
if transactions_manager:
transactions_manager.commit()
endInteraction()
notify(PubSuccess(request))
return response
except:
# save in order to give 'PubFailure' the original exception info
exc_info = sys.exc_info()
# DM: provide nicer error message for FTP
sm = None
if response is not None:
sm = getattr(response, "setMessage", None)
if sm is not None:
from asyncore import compact_traceback
cl,val= sys.exc_info()[:2]
sm('%s: %s %s' % (
getattr(cl,'__name__',cl), val,
debug_mode and compact_traceback()[-1] or ''))
# debug is just used by tests (has nothing to do with debug_mode!)
if not debug and err_hook is not None:
retry = False
if parents:
parents=parents[0]
try:
try:
with getPublisherDeadlineValue(request):
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
except Retry:
if not request.supports_retry(request):
with getPublisherDeadlineValue():
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
retry = True
finally:
# Note: 'abort's can fail. Nevertheless, we want end request handling
try:
try:
notify(PubBeforeAbort(request, exc_info, retry))
finally:
if transactions_manager:
transactions_manager.abort()
finally:
endInteraction()
notify(PubFailure(request, exc_info, retry))
# Only reachable if Retry is raised and request supports retry.
newrequest=request.retry()
request.close() # Free resources held by the request.
# Set the default layer/skin on the newly generated request
if ISkinnable.providedBy(newrequest):
setDefaultSkin(newrequest)
try:
return publish(newrequest, module_name, after_list, debug)
finally:
newrequest.close()
call_object_orig = Publish.call_object
call_object = wrap_call_object(call_object_orig)
Publish.call_object = call_object
else:
# Note: 'abort's can fail. Nevertheless, we want end request handling
try:
try:
notify(PubBeforeAbort(request, exc_info, False))
finally:
if transactions_manager:
transactions_manager.abort()
finally:
endInteraction()
notify(PubFailure(request, exc_info, False))
raise
publish = Publish.__dict__['publish']
assert publish.__module__ == 'ZPublisher.Publish', repr(publish.__module__)
if publish.__name__ == 'new_publish': # already patched by Localizer/patches.py
publish = publish.__defaults__[1]
publish.__defaults__ = tuple(call_object if x is call_object_orig else x for x in publish.__defaults__)
Publish.publish = publish
......@@ -33,7 +33,7 @@ from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import aq_acquire
from Acquisition import aq_inner
from Acquisition import aq_parent
from Products.ERP5Type.Timeout import wrap_call_object
from Products.ERP5Type.Timeout import getPublisherDeadlineValue
from transaction.interfaces import TransientError
from zExceptions import Redirect
from zExceptions import Unauthorized
......@@ -51,7 +51,7 @@ from ZPublisher.HTTPResponse import HTTPResponse
from ZPublisher.HTTPRequest import HTTPRequest
from ZPublisher.Iterators import IStreamIterator, IUnboundStreamIterator
from ZPublisher.mapply import mapply
from ZPublisher.WSGIPublisher import call_object as call_object_orig
from ZPublisher.WSGIPublisher import call_object
from ZPublisher.WSGIPublisher import missing_name, WSGIResponse
......@@ -64,7 +64,6 @@ _DEFAULT_DEBUG_MODE = False
_DEFAULT_REALM = None
_MODULE_LOCK = allocate_lock()
_MODULES = {}
call_object = wrap_call_object(call_object_orig)
AC_LOGGER = logging.getLogger('event.AccessControl')
......@@ -342,41 +341,42 @@ def transaction_pubevents(request, response, err_hook, tm=transaction.manager):
def publish(request, module_info):
obj, realm, debug_mode = module_info
request.processInputs()
response = request.response
if debug_mode:
response.debug_mode = debug_mode
if realm and not request.get('REMOTE_USER', None):
response.realm = realm
noSecurityManager()
# Get the path list.
# According to RFC1738 a trailing space in the path is valid.
path = request.get('PATH_INFO')
request['PARENTS'] = [obj]
obj = request.traverse(path, validated_hook=validated_hook)
notify(pubevents.PubAfterTraversal(request))
recordMetaData(obj, request)
result = mapply(obj,
request.args,
request,
call_object,
1,
missing_name,
dont_publish_class,
request,
bind=1)
if result is not response:
response.setBody(result)
return response
with getPublisherDeadlineValue(request):
obj, realm, debug_mode = module_info
request.processInputs()
response = request.response
if debug_mode:
response.debug_mode = debug_mode
if realm and not request.get('REMOTE_USER', None):
response.realm = realm
noSecurityManager()
# Get the path list.
# According to RFC1738 a trailing space in the path is valid.
path = request.get('PATH_INFO')
request['PARENTS'] = [obj]
obj = request.traverse(path, validated_hook=validated_hook)
notify(pubevents.PubAfterTraversal(request))
recordMetaData(obj, request)
result = mapply(obj,
request.args,
request,
call_object,
1,
missing_name,
dont_publish_class,
request,
bind=1)
if result is not response:
response.setBody(result)
return response
@contextmanager
......
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