Files
odoo_source/odoo/service/wsgi_server.py
T
Xavier Morel 759e2c5829 [FIX] core: avoid feeding client invalid XML-RPC documents
The XML-RPC interface has a compatibility shim for binaries as
historically Odoo has returned "binary" data as base64 strings. To
avoid breakages during the Python 3 transition, the shim was
introduced to decode the output binary data (under the assumption that
it'd be ASCII-compatible).

In the case where the data is *not* ascii-compatible, however, it can
generate invalid XML documents: "C0" control codes (with the exception
of tab, LF, and CR) are not valid in XML 1.0 (which XML-RPC is an
application of), however they're perfectly valid string characters and
the standard library's marshaller does not check for them, embedding
them directly in the output document and breaking the client's
decoding.

Work around the issue by replacing such binary data with an empty
string.

While at it, move the bytes shim to the customized marshaller, this
way everything's at the same place and it's not necessary to waste
time trying to understand why the marshaller is just not calling what
it's supposed to call.

Fixes #61919

closes odoo/odoo#75973

Forward-port-of: #75952
Forward-port-of: #74699
X-original-commit: 1a0b3f7
Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
2021-09-06 12:03:31 +00:00

113 lines
4.4 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
"""
WSGI stack, common code.
"""
import logging
import sys
import threading
import traceback
from xmlrpc import client as xmlrpclib
import werkzeug.exceptions
import werkzeug.wrappers
import werkzeug.serving
import odoo
from odoo.tools import config
_logger = logging.getLogger(__name__)
# XML-RPC fault codes. Some care must be taken when changing these: the
# constants are also defined client-side and must remain in sync.
# User code must use the exceptions defined in ``odoo.exceptions`` (not
# create directly ``xmlrpclib.Fault`` objects).
RPC_FAULT_CODE_CLIENT_ERROR = 1 # indistinguishable from app. error.
RPC_FAULT_CODE_APPLICATION_ERROR = 1
RPC_FAULT_CODE_WARNING = 2
RPC_FAULT_CODE_ACCESS_DENIED = 3
RPC_FAULT_CODE_ACCESS_ERROR = 4
def xmlrpc_handle_exception_int(e):
if isinstance(e, odoo.exceptions.RedirectWarning):
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
elif isinstance(e, odoo.exceptions.AccessError):
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_ERROR, str(e))
elif isinstance(e, odoo.exceptions.AccessDenied):
fault = xmlrpclib.Fault(RPC_FAULT_CODE_ACCESS_DENIED, str(e))
elif isinstance(e, odoo.exceptions.UserError):
fault = xmlrpclib.Fault(RPC_FAULT_CODE_WARNING, str(e))
else:
info = sys.exc_info()
# Which one is the best ?
formatted_info = "".join(traceback.format_exception(*info))
#formatted_info = odoo.tools.exception_to_unicode(e) + '\n' + info
fault = xmlrpclib.Fault(RPC_FAULT_CODE_APPLICATION_ERROR, formatted_info)
return xmlrpclib.dumps(fault, allow_none=None)
def xmlrpc_handle_exception_string(e):
if isinstance(e, odoo.exceptions.RedirectWarning):
fault = xmlrpclib.Fault('warning -- Warning\n\n' + str(e), '')
elif isinstance(e, odoo.exceptions.MissingError):
fault = xmlrpclib.Fault('warning -- MissingError\n\n' + str(e), '')
elif isinstance(e, odoo.exceptions.AccessError):
fault = xmlrpclib.Fault('warning -- AccessError\n\n' + str(e), '')
elif isinstance(e, odoo.exceptions.AccessDenied):
fault = xmlrpclib.Fault('AccessDenied', str(e))
elif isinstance(e, odoo.exceptions.UserError):
fault = xmlrpclib.Fault('warning -- UserError\n\n' + str(e), '')
#InternalError
else:
info = sys.exc_info()
formatted_info = "".join(traceback.format_exception(*info))
fault = xmlrpclib.Fault(odoo.tools.exception_to_unicode(e), formatted_info)
return xmlrpclib.dumps(fault, allow_none=None, encoding=None)
def application_unproxied(environ, start_response):
""" WSGI entry point."""
# cleanup db/uid trackers - they're set at HTTP dispatch in
# web.session.OpenERPSession.send() and at RPC dispatch in
# odoo.service.web_services.objects_proxy.dispatch().
# /!\ The cleanup cannot be done at the end of this `application`
# method because werkzeug still produces relevant logging afterwards
if hasattr(threading.current_thread(), 'uid'):
del threading.current_thread().uid
if hasattr(threading.current_thread(), 'dbname'):
del threading.current_thread().dbname
if hasattr(threading.current_thread(), 'url'):
del threading.current_thread().url
result = odoo.http.root(environ, start_response)
if result is not None:
return result
# We never returned from the loop.
return werkzeug.exceptions.NotFound("No handler found.\n")(environ, start_response)
try:
# werkzeug >= 0.15
from werkzeug.middleware.proxy_fix import ProxyFix as ProxyFix_
# 0.15 also supports port and prefix, but 0.14 only forwarded for, proto
# and host so replicate that
ProxyFix = lambda app: ProxyFix_(app, x_for=1, x_proto=1, x_host=1)
except ImportError:
# werkzeug < 0.15
from werkzeug.contrib.fixers import ProxyFix
def application(environ, start_response):
# FIXME: is checking for the presence of HTTP_X_FORWARDED_HOST really useful?
# we're ignoring the user configuration, and that means we won't
# support the standardised Forwarded header once werkzeug supports
# it
if config['proxy_mode'] and 'HTTP_X_FORWARDED_HOST' in environ:
return ProxyFix(application_unproxied)(environ, start_response)
else:
return application_unproxied(environ, start_response)