Files
tryton/trytond/protocols/xmlrpc.py
T

240 lines
8.5 KiB
Python

#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from trytond.protocols.sslsocket import SSLSocket
from trytond.protocols.dispatcher import dispatch
from trytond.config import CONFIG
from trytond.protocols.datatype import Float
from trytond import security
import SimpleXMLRPCServer
import SocketServer
import xmlrpclib
import threading
import traceback
import socket
import sys
import os
import gzip
import StringIO
import base64
# convert decimal to float before marshalling:
from decimal import Decimal
xmlrpclib.Marshaller.dispatch[Decimal] = \
lambda self, value, write: self.dump_double(float(value), write)
xmlrpclib.Marshaller.dispatch[type(None)] = \
lambda self, value, write: self.dump_bool(bool(value), write)
def _end_double(self, data):
self.append(Float(data))
self._value = 0
xmlrpclib.Unmarshaller.dispatch["double"] = _end_double
class GenericXMLRPCRequestHandler:
def _dispatch(self, method, params):
host, port = self.client_address[:2]
database_name = self.path[1:]
user = self.tryton['user']
session = self.tryton['session']
try:
try:
method_list = method.split('.')
object_type = method_list[0]
object_name = '.'.join(method_list[1:-1])
method = method_list[-1]
if object_type == 'system' and method == 'getCapabilities':
return {
'introspect': {
'specUrl': 'http://xmlrpc-c.sourceforge.net/' \
'xmlrpc-c/introspection.html',
'specVersion': 1,
},
}
return dispatch(host, port, 'XML-RPC', database_name, user,
session, object_type, object_name, method, *params)
except:
tb_s = ''
for line in traceback.format_exception(*sys.exc_info()):
try:
line = line.encode('utf-8', 'ignore')
except:
continue
tb_s += line
for path in sys.path:
tb_s = tb_s.replace(path, '')
if CONFIG['debug_mode']:
import pdb
traceb = sys.exc_info()[2]
pdb.post_mortem(traceb)
raise xmlrpclib.Fault(1, str(sys.exc_value) + '\n' + tb_s)
finally:
security.logout(database_name, user, session)
class SimpleXMLRPCRequestHandler(GenericXMLRPCRequestHandler,
SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
rpc_paths = None
encode_threshold = 1400 # common MTU
def parse_request(self):
res = SimpleXMLRPCServer.SimpleXMLRPCRequestHandler.parse_request(self)
database_name = self.path[1:]
if not database_name:
self.tryton = {'user': None, 'session': None}
return res
try:
method, up64 = self.headers['Authorization'].split(None, 1)
if method.strip().lower() == 'basic':
user, password = base64.decodestring(up64).split(':', 1)
user_id, session = security.login(database_name, user,
password)
self.tryton = {'user': user_id, 'session': session}
return res
except:
pass
self.send_error(401, 'Unauthorized')
self.send_header("WWW-Authenticate", 'Basic realm="Tryton"')
return False
# Copy from SimpleXMLRPCServer.py with gzip encoding added
def do_POST(self):
"""Handles the HTTP POST request.
Attempts to interpret all HTTP POST requests as XML-RPC calls,
which are forwarded to the server's _dispatch method for handling.
"""
# Check that the path is legal
if not self.is_rpc_path_valid():
self.report_404()
return
try:
# Get arguments by reading body of request.
# We read this in chunks to avoid straining
# socket.read(); around the 10 or 15Mb mark, some platforms
# begin to have problems (bug #792570).
max_chunk_size = 10*1024*1024
size_remaining = int(self.headers["content-length"])
L = []
while size_remaining:
chunk_size = min(size_remaining, max_chunk_size)
L.append(self.rfile.read(chunk_size))
size_remaining -= len(L[-1])
data = ''.join(L)
# In previous versions of SimpleXMLRPCServer, _dispatch
# could be overridden in this class, instead of in
# SimpleXMLRPCDispatcher. To maintain backwards compatibility,
# check to see if a subclass implements _dispatch and dispatch
# using that method if present.
response = self.server._marshaled_dispatch(
data, getattr(self, '_dispatch', None)
)
except: # This should only happen if the module is buggy
# internal error, report as HTTP server error
self.send_response(500)
self.end_headers()
else:
# got a valid XML RPC response
self.send_response(200)
self.send_header("Content-type", "text/xml")
# Handle gzip encoding
if 'gzip' in self.headers.get('Accept-Encoding', '').split(',') \
and len(response) > self.encode_threshold:
buffer = StringIO.StringIO()
output = gzip.GzipFile(mode='wb', fileobj=buffer)
output.write(response)
output.close()
buffer.seek(0)
response = buffer.getvalue()
self.send_header('Content-Encoding', 'gzip')
self.send_header("Content-length", str(len(response)))
self.end_headers()
self.wfile.write(response)
# shut down the connection
self.wfile.flush()
self.connection.shutdown(1)
class SecureXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):
def setup(self):
self.connection = SSLSocket(self.request)
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
class SimpleThreadedXMLRPCServer(SocketServer.ThreadingMixIn,
SimpleXMLRPCServer.SimpleXMLRPCServer):
timeout = 1
def server_bind(self):
self.socket.setsockopt(socket.SOL_SOCKET,
socket.SO_REUSEADDR, 1)
SimpleXMLRPCServer.SimpleXMLRPCServer.server_bind(self)
class SimpleThreadedXMLRPCServer6(SimpleThreadedXMLRPCServer):
address_family = socket.AF_INET6
class SecureThreadedXMLRPCServer(SimpleThreadedXMLRPCServer):
def __init__(self, server_address, HandlerClass, logRequests=1):
SimpleThreadedXMLRPCServer.__init__(self, server_address, HandlerClass,
logRequests)
self.socket = SSLSocket(socket.socket(self.address_family,
self.socket_type))
self.server_bind()
self.server_activate()
class SecureThreadedXMLRPCServer6(SecureThreadedXMLRPCServer):
address_family = socket.AF_INET6
class XMLRPCDaemon(threading.Thread):
def __init__(self, interface, port, secure=False):
threading.Thread.__init__(self)
self.secure = secure
self.running = False
ipv6 = False
if socket.has_ipv6:
try:
socket.getaddrinfo(interface or None, port, socket.AF_INET6)
ipv6 = True
except:
pass
if secure:
handler_class = SecureXMLRPCRequestHandler
server_class = SecureThreadedXMLRPCServer
if ipv6:
server_class = SecureThreadedXMLRPCServer6
else:
handler_class = SimpleXMLRPCRequestHandler
server_class = SimpleThreadedXMLRPCServer
if ipv6:
server_class = SimpleThreadedXMLRPCServer6
self.server = server_class((interface, port), handler_class, 0)
def stop(self):
self.running = False
if os.name != 'nt':
if hasattr(socket, 'SHUT_RDWR'):
self.server.socket.shutdown(socket.SHUT_RDWR)
else:
self.server.socket.shutdown(2)
self.server.socket.close()
def run(self):
self.running = True
while self.running:
self.server.handle_request()
return True