Commit 26370f24 authored by Hanno Schlichting's avatar Hanno Schlichting

Move xmlrpc code into ZServer distribution.

parent 478ae3a2
......@@ -15,12 +15,12 @@
from urllib import quote as urllib_quote
import types
import xmlrpc
from AccessControl.ZopeSecurityPolicy import getRoles
from Acquisition import aq_base, aq_inner
from Acquisition.interfaces import IAcquirer
from ExtensionClass import Base
import pkg_resources
from zExceptions import Forbidden
from zExceptions import NotFound
from zope.component import queryMultiAdapter
......@@ -39,6 +39,14 @@ from zope.traversing.namespace import nsParse
from ZPublisher.Converters import type_converters
from ZPublisher.interfaces import UseTraversalDefault
try:
dist = pkg_resources.get_distribution('ZServer')
except pkg_resources.DistributionNotFound:
def is_xmlrpc_response(response):
return False
else:
from ZServer.ZPublisher.xmlrpc import is_xmlrpc_response
_marker = []
UNSPECIFIED_ROLES = ''
......@@ -386,8 +394,8 @@ class BaseRequest:
# How did this request come in? (HTTP GET, PUT, POST, etc.)
method = request_get('REQUEST_METHOD', 'GET').upper()
if (method in ["GET", "POST", "PURGE"] and
not isinstance(response, xmlrpc.Response)):
if (method in ('GET', 'POST', 'PURGE') and
not is_xmlrpc_response(response)):
# Probably a browser
no_acquire_flag = 0
# index_html is still the default method, only any object can
......
......@@ -14,6 +14,7 @@
""" HTTP request management.
"""
import base64
from cgi import escape
from cgi import FieldStorage
import codecs
......@@ -33,6 +34,8 @@ from urllib import unquote
from urllib import splittype
from urllib import splitport
from AccessControl.tainted import TaintedString
import pkg_resources
from zope.i18n.interfaces import IUserPreferredLanguages
from zope.i18n.locales import locales, LoadLocaleError
from zope.interface import directlyProvidedBy
......@@ -41,7 +44,6 @@ from zope.interface import implements
from zope.publisher.base import DebugFlags
from zope.publisher.interfaces.browser import IBrowserRequest
from AccessControl.tainted import TaintedString
from ZPublisher.BaseRequest import BaseRequest
from ZPublisher.BaseRequest import quote
from ZPublisher.Converters import get_converter
......@@ -49,6 +51,14 @@ from ZPublisher.Converters import get_converter
if sys.version_info >= (3, ):
unicode = str
xmlrpc = None
try:
dist = pkg_resources.get_distribution('ZServer')
except pkg_resources.DistributionNotFound:
pass
else:
from ZServer.ZPublisher import xmlrpc
# Flags
SEQUENCE = 1
DEFAULT = 2
......@@ -58,10 +68,6 @@ REC = RECORD | RECORDS
EMPTY = 16
CONVERTED = 32
# Placeholders for module that we'll import if we have to.
xmlrpc = None
base64 = None
# This may get overwritten during configuration
default_encoding = 'utf-8'
......@@ -504,14 +510,10 @@ class HTTPRequest(BaseRequest):
if 'HTTP_SOAPACTION' in environ:
# Stash XML request for interpretation by a SOAP-aware view
other['SOAPXML'] = fs.value
# Hm, maybe it's an XML-RPC
elif ('content-type' in fs.headers and
'text/xml' in fs.headers['content-type'] and
method == 'POST'):
elif (xmlrpc is not None and method == 'POST' and
('content-type' in fs.headers and
'text/xml' in fs.headers['content-type'])):
# Ye haaa, XML-RPC!
global xmlrpc
if xmlrpc is None:
from ZPublisher import xmlrpc
meth, self.args = xmlrpc.parse_input(fs.value)
response = xmlrpc.response(response)
other['RESPONSE'] = self.response = response
......@@ -1528,12 +1530,9 @@ class HTTPRequest(BaseRequest):
return result
def _authUserPW(self):
global base64
auth = self._auth
if auth:
if auth[:6].lower() == 'basic ':
if base64 is None:
import base64
[name, password] = \
base64.decodestring(auth.split()[-1]).split(':', 1)
return name, password
......
......@@ -517,8 +517,7 @@ class HTTPResponse(BaseResponse):
This function is designed to be called on each request to specify
on a request-by-request basis that the response content should
be compressed. This is quite useful for xml-rpc transactions, where
compression rates of 90% or more can be achieved for text data.
be compressed.
The REQUEST headers are used to determine if the client accepts
gzip content encoding. The force parameter can force the use
......@@ -705,11 +704,6 @@ class HTTPResponse(BaseResponse):
et = translate(str(t), nl2sp)
self.setHeader('bobo-exception-type', et)
# As of Zope 2.6.2 / 2.7, we no longer try to pass along a
# meaningful exception value. Now that there are good logging
# facilities on the server side (and given that xml-rpc has
# largely removed the need for this code at all), we just
# refer the caller to the server error log.
ev = 'See the server error log for details'
self.setHeader('bobo-exception-value', ev)
......
......@@ -32,29 +32,11 @@ from zope.security.management import newInteraction, endInteraction
from ZPublisher.mapply import mapply
from ZPublisher import pubevents
from ZPublisher import Retry
from ZPublisher.HTTPRequest import HTTPRequest as Request
from ZPublisher.HTTPResponse import HTTPResponse as Response
class Retry(Exception):
"""Raise this to retry a request
"""
def __init__(self, t=None, v=None, tb=None):
self._args = t, v, tb
def reraise(self):
t, v, tb = self._args
if t is None:
t = Retry
if tb is None:
raise t(v)
try:
reraise(t, v, tb)
finally:
tb = None
def call_object(object, args, request):
return object(*args)
......
......@@ -11,6 +11,25 @@
#
##############################################################################
from six import reraise
from zExceptions import NotFound, BadRequest, InternalError, Forbidden # NOQA
from ZPublisher.Publish import publish_module, Retry # NOQA
class Retry(Exception):
"""Raise this to retry a request
"""
def __init__(self, t=None, v=None, tb=None):
self._args = t, v, tb
def reraise(self):
t, v, tb = self._args
if t is None:
t = Retry
if tb is None:
raise t(v)
try:
reraise(t, v, tb)
finally:
tb = None
......@@ -12,7 +12,7 @@
##############################################################################
import unittest
from ZPublisher.Publish import get_module_info
from ZPublisher.WSGIPublisher import get_module_info
class WSGIResponseTests(unittest.TestCase):
......
import unittest
from DateTime import DateTime
class FauxResponse:
def __init__(self):
self._headers = {}
self._body = None
def setBody(self, body):
self._body = body
def setHeader(self, name, value):
self._headers[name] = value
def setStatus(self, status):
self._status = status
class FauxInstance:
def __init__(self, **kw):
self.__dict__.update(kw)
class XMLRPCResponseTests(unittest.TestCase):
def _getTargetClass(self):
from ZPublisher.xmlrpc import Response
return Response
def _makeOne(self, *args, **kw):
return self._getTargetClass()(*args, **kw)
def test_setBody(self):
import xmlrpclib
body = FauxInstance(_secret='abc', public='def')
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
body_str = faux._body
self.assertEqual(type(body_str), type(''))
as_set, method = xmlrpclib.loads(body_str)
as_set = as_set[0]
self.assertEqual(method, None)
self.assertFalse('_secret' in as_set.keys())
self.assertTrue('public' in as_set.keys())
self.assertEqual(as_set['public'], 'def')
def test_nil(self):
import xmlrpclib
body = FauxInstance(public=None)
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
self.assert_(data[0]['public'] is None)
def test_instance(self):
# Instances are turned into dicts with their private
# attributes removed.
import xmlrpclib
body = FauxInstance(_secret='abc', public='def')
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]
self.assertEqual(data, {'public': 'def'})
def test_instanceattribute(self):
# While the removal of private ('_') attributes works fine for the
# top-level instance, how about attributes that are themselves
# instances?
import xmlrpclib
body = FauxInstance(public=FauxInstance(_secret='abc', public='def'))
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['public']
self.assertEqual(data, {'public': 'def'})
def test_instanceattribute_recursive(self):
# Instance "flattening" should work recursively, ad infinitum
import xmlrpclib
body = FauxInstance(public=FauxInstance(public=FauxInstance(
_secret='abc', public='def')))
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['public']['public']
self.assertEqual(data, {'public': 'def'})
def test_instance_in_list(self):
# Instances are turned into dicts with their private
# attributes removed, even when embedded in another
# data structure.
import xmlrpclib
body = [FauxInstance(_secret='abc', public='def')]
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0][0]
self.assertEqual(data, {'public': 'def'})
def test_instance_in_dict(self):
# Instances are turned into dicts with their private
# attributes removed, even when embedded in another
# data structure.
import xmlrpclib
body = {'faux': FauxInstance(_secret='abc', public='def')}
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['faux']
self.assertEqual(data, {'public': 'def'})
def test_zopedatetimeinstance(self):
# DateTime instance at top-level
import xmlrpclib
body = DateTime('2006-05-24 07:00:00 GMT+0')
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]
self.assertTrue(isinstance(data, xmlrpclib.DateTime))
self.assertEqual(data.value, u'2006-05-24T07:00:00+00:00')
def test_zopedatetimeattribute(self):
# DateTime instance as attribute
import xmlrpclib
body = FauxInstance(public=DateTime('2006-05-24 07:00:00 GMT+0'))
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['public']
self.assertTrue(isinstance(data, xmlrpclib.DateTime))
self.assertEqual(data.value, u'2006-05-24T07:00:00+00:00')
def test_zopedatetimeattribute_recursive(self):
# DateTime encoding should work recursively
import xmlrpclib
body = FauxInstance(public=FauxInstance(
public=DateTime('2006-05-24 07:00:00 GMT+0')))
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['public']['public']
self.assertTrue(isinstance(data, xmlrpclib.DateTime))
self.assertEqual(data.value, u'2006-05-24T07:00:00+00:00')
def test_zopedatetimeinstance_in_list(self):
# DateTime instance embedded in a list
import xmlrpclib
body = [DateTime('2006-05-24 07:00:00 GMT+0')]
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0][0]
self.assertTrue(isinstance(data, xmlrpclib.DateTime))
self.assertEqual(data.value, u'2006-05-24T07:00:00+00:00')
def test_zopedatetimeinstance_in_dict(self):
# DateTime instance embedded in a dict
import xmlrpclib
body = {'date': DateTime('2006-05-24 07:00:00 GMT+0')}
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]['date']
self.assertTrue(isinstance(data, xmlrpclib.DateTime))
self.assertEqual(data.value, u'2006-05-24T07:00:00+00:00')
def test_functionattribute(self):
# Cannot marshal functions or methods, obviously
import xmlrpclib
def foo():
pass
body = FauxInstance(public=foo)
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
func = xmlrpclib.loads(faux._body)
self.assertEqual(func, (({'public': {}},), None))
def test_emptystringattribute(self):
# Test an edge case: attribute name '' is possible,
# at least in theory.
import xmlrpclib
body = FauxInstance(_secret='abc')
setattr(body, '', True)
faux = FauxResponse()
response = self._makeOne(faux)
response.setBody(body)
data, method = xmlrpclib.loads(faux._body)
data = data[0]
self.assertEqual(data, {'': True})
......@@ -10,193 +10,15 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""XML-RPC support module
Written by Eric Kidd at UserLand software, with much help from Jim Fulton
at DC. This code hooks Zope up to Fredrik Lundh's Python XML-RPC library.
See http://www.xmlrpc.com/ and http://linux.userland.com/ for more
information about XML-RPC and Zope.
"""
import re
import sys
import types
import xmlrpclib
from App.config import getConfiguration
from zExceptions import Unauthorized
from ZODB.POSException import ConflictError
# Make DateTime.DateTime marshallable via XML-RPC
# http://www.zope.org/Collectors/Zope/2109
from DateTime.DateTime import DateTime
WRAPPERS = xmlrpclib.WRAPPERS + (DateTime, )
def dump_instance(self, value, write):
# Check for special wrappers
if value.__class__ in WRAPPERS:
self.write = write
value.encode(self)
del self.write
else:
# Store instance attributes as a struct (really?).
# We want to avoid disclosing private attributes.
# Private attributes are by convention named with
# a leading underscore character.
value = dict([(k, v) for (k, v) in value.__dict__.items()
if k[:1] != '_'])
self.dump_struct(value, write)
xmlrpclib.Marshaller.dispatch[types.InstanceType] = dump_instance
xmlrpclib.Marshaller.dispatch[DateTime] = dump_instance
def parse_input(data):
"""Parse input data and return a method path and argument tuple
The data is a string.
"""
#
# For example, with the input:
#
# <?xml version="1.0"?>
# <methodCall>
# <methodName>examples.getStateName</methodName>
# <params>
# <param>
# <value><i4>41</i4></value>
# </param>
# </params>
# </methodCall>
#
# the function should return:
#
# ('examples.getStateName', (41,))
params, method = xmlrpclib.loads(data)
# Translate '.' to '/' in meth to represent object traversal.
method = method.replace('.', '/')
return method, params
# See below
#
# def response(anHTTPResponse):
# """Return a valid ZPublisher response object
#
# Use data already gathered by the existing response.
# The new response will replace the existing response.
# """
# # As a first cut, lets just clone the response and
# # put all of the logic in our refined response class below.
# r=Response()
# r.__dict__.update(anHTTPResponse.__dict__)
# return r
########################################################################
# Possible implementation helpers:
class Response:
"""Customized Response that handles XML-RPC-specific details.
We override setBody to marhsall Python objects into XML-RPC. We
also override exception to convert errors to XML-RPC faults.
If these methods stop getting called, make sure that ZPublisher is
using the xmlrpc.Response object created above and not the original
HTTPResponse object from which it was cloned.
It's probably possible to improve the 'exception' method quite a bit.
The current implementation, however, should suffice for now.
"""
_error_format = 'text/plain' # No html in error values
# Because we can't predict what kind of thing we're customizing,
# we have to use delegation, rather than inheritance to do the
# customization.
def __init__(self, real):
self.__dict__['_real'] = real
def __getattr__(self, name):
return getattr(self._real, name)
def __setattr__(self, name, v):
return setattr(self._real, name, v)
def __delattr__(self, name):
return delattr(self._real, name)
def setBody(self, body, title='', is_error=0, bogus_str_search=None):
if isinstance(body, xmlrpclib.Fault):
# Convert Fault object to XML-RPC response.
body = xmlrpclib.dumps(body, methodresponse=1, allow_none=True)
else:
# Marshall our body as an XML-RPC response. Strings will be sent
# strings, integers as integers, etc. We do *not* convert
# everything to a string first.
# Previously this had special handling if the response
# was a Python None. This is now patched in xmlrpclib to
# allow Nones nested inside data structures too.
try:
body = xmlrpclib.dumps(
(body,), methodresponse=1, allow_none=True)
except ConflictError:
raise
except:
self.exception()
return
# Set our body to the XML-RPC message, and fix our MIME type.
self._real.setBody(body)
self._real.setHeader('content-type', 'text/xml')
return self
def exception(self, fatal=0, info=None,
absuri_match=None, tag_search=None):
# Fetch our exception info. t is type, v is value and tb is the
# traceback object.
if isinstance(info, tuple) and len(info) == 3:
t, v, tb = info
else:
t, v, tb = sys.exc_info()
# Don't mask 404 respnses, as some XML-RPC libraries rely on the HTTP
# mechanisms for detecting when authentication is required. Fixes Zope
# Collector issue 525.
if issubclass(t, Unauthorized):
return self._real.exception(fatal=fatal, info=info)
# Create an appropriate Fault object. Containing error information
Fault = xmlrpclib.Fault
f = None
try:
# Strip HTML tags from the error value
vstr = str(v)
remove = [r"<[^<>]*>", r"&[A-Za-z]+;"]
for pat in remove:
vstr = re.sub(pat, " ", vstr)
if getConfiguration().debug_mode:
from traceback import format_exception
value = '\n' + ''.join(format_exception(t, vstr, tb))
else:
value = '%s - %s' % (t, vstr)
if isinstance(v, Fault):
f = v
elif isinstance(v, Exception):
f = Fault(-1, 'Unexpected Zope exception: %s' % value)
else:
f = Fault(-2, 'Unexpected Zope error value: %s' % value)
except ConflictError:
raise
except Exception:
f = Fault(-3, "Unknown Zope fault type")
# Do the damage.
self.setBody(f)
self._real.setStatus(200)
return tb
response = Response
from zope.deferredimport import deprecated
# BBB Zope 5.0
deprecated(
'Please import from ZServer.ZPublisher.xmlrpc.',
dump_instance='ZServer.ZPublisher.xmlrpc:dump_instance',
parse_input='ZServer.ZPublisher.xmlrpc:parse_input',
response='ZServer.ZPublisher.xmlrpc:response',
Response='ZServer.ZPublisher.xmlrpc:Response',
WRAPPERS='ZServer.ZPublisher.xmlrpc:WRAPPERS',
)
......@@ -27,7 +27,7 @@ import OFS.Application
import transaction
import ZODB
import Zope2
import ZPublisher
from ZPublisher import Retry
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import aq_acquire
......@@ -200,9 +200,9 @@ class ZPublisherExceptionHook:
if issubclass(t, ConflictError):
self.logConflicts(v, REQUEST)
raise ZPublisher.Retry(t, v, traceback)
raise Retry(t, v, traceback)
if t is ZPublisher.Retry:
if t is Retry:
try:
v.reraise()
except:
......
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