From 483bc966826f46de4299866cdd01a958157ff499 Mon Sep 17 00:00:00 2001 From: Antony Lesuisse Date: Wed, 26 Mar 2014 14:20:57 +0100 Subject: [PATCH] [IMP] wsgi and http cleanups, static http is now handled in http.py bzr revid: al@openerp.com-20140326132057-scuiqvqma9dhyorl --- openerp/service/http_server.py | 178 --------------------------------- openerp/service/wsgi_server.py | 149 +-------------------------- openerp/tools/config.py | 9 -- 3 files changed, 1 insertion(+), 335 deletions(-) delete mode 100644 openerp/service/http_server.py diff --git a/openerp/service/http_server.py b/openerp/service/http_server.py deleted file mode 100644 index e25e75dba62..00000000000 --- a/openerp/service/http_server.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright P. Christeas 2008-2010 -# Copyright 2010 OpenERP SA. (http://www.openerp.com) -# -# -# WARNING: This program as such is intended to be used by professional -# programmers who take the whole responsibility 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 -# guarantees and support are strongly advised to contract a Free Software -# Service Company -# -# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -############################################################################### - - -""" This module offers the family of HTTP-based servers. These are not a single - class/functionality, but a set of network stack layers, implementing - extendable HTTP protocols. - - The OpenERP server defines a single instance of a HTTP server, listening at - the standard 8069, 8071 ports (well, it is 2 servers, and ports are - configurable, of course). This "single" server then uses a `MultiHTTPHandler` - to dispatch requests to the appropriate channel protocol, like the XML-RPC, - static HTTP, DAV or other. -""" - -import base64 -import posixpath -import urllib -import os -import logging - -from websrv_lib import * -import openerp.tools as tools - -try: - import fcntl -except ImportError: - fcntl = None - -try: - from ssl import SSLError -except ImportError: - class SSLError(Exception): pass - -_logger = logging.getLogger(__name__) - -# TODO delete this for 6.2, it is still needed for 6.1. -class HttpLogHandler: - """ helper class for uniform log handling - Please define self._logger at each class that is derived from this - """ - _logger = None - - def log_message(self, format, *args): - self._logger.debug(format % args) # todo: perhaps other level - - def log_error(self, format, *args): - self._logger.error(format % args) - - def log_exception(self, format, *args): - self._logger.exception(format, *args) - - def log_request(self, code='-', size='-'): - self._logger.debug('"%s" %s %s', - self.requestline, str(code), str(size)) - -class StaticHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler): - _logger = logging.getLogger(__name__) - - _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD'] } - - def __init__(self,request, client_address, server): - HTTPHandler.__init__(self,request,client_address,server) - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Please specify static_http_document_root in configuration, or disable static-httpd!" - self.__basepath = document_root - - def translate_path(self, path): - """Translate a /-separated PATH to the local filename syntax. - - Components that mean special things to the local file system - (e.g. drive or directory names) are ignored. (XXX They should - probably be diagnosed.) - - """ - # abandon query parameters - path = path.split('?',1)[0] - path = path.split('#',1)[0] - path = posixpath.normpath(urllib.unquote(path)) - words = path.split('/') - words = filter(None, words) - path = self.__basepath - for word in words: - if word in (os.curdir, os.pardir): continue - path = os.path.join(path, word) - return path - -def init_static_http(): - if not tools.config.get('static_http_enable', False): - return - - document_root = tools.config.get('static_http_document_root', False) - assert document_root, "Document root must be specified explicitly to enable static HTTP service (option --static-http-document-root)" - - base_path = tools.config.get('static_http_url_prefix', '/') - - reg_http_service(base_path, StaticHTTPHandler) - - _logger.info("Registered HTTP dir %s for %s", document_root, base_path) - -import security - -class OpenERPAuthProvider(AuthProvider): - """ Require basic authentication.""" - def __init__(self,realm='OpenERP User'): - self.realm = realm - self.auth_creds = {} - self.auth_tries = 0 - self.last_auth = None - - def authenticate(self, db, user, passwd, client_address): - try: - uid = security.login(db,user,passwd) - if uid is False: - return False - return user, passwd, db, uid - except Exception,e: - _logger.debug("Fail auth: %s" % e ) - return False - - def checkRequest(self,handler,path, db=False): - auth_str = handler.headers.get('Authorization',False) - try: - if not db: - db = handler.get_db_from_path(path) - except Exception: - if path.startswith('/'): - path = path[1:] - psp= path.split('/') - if len(psp)>1: - db = psp[0] - else: - #FIXME! - _logger.info("Wrong path: %s, failing auth" %path) - raise AuthRejectedExc("Authorization failed. Wrong sub-path.") - if self.auth_creds.get(db): - return True - if auth_str and auth_str.startswith('Basic '): - auth_str=auth_str[len('Basic '):] - (user,passwd) = base64.decodestring(auth_str).split(':') - _logger.info("Found user=\"%s\", passwd=\"***\" for db=\"%s\"", user, db) - acd = self.authenticate(db,user,passwd,handler.client_address) - if acd != False: - self.auth_creds[db] = acd - self.last_auth = db - return True - if self.auth_tries > 5: - _logger.info("Failing authorization after 5 requests w/o password") - raise AuthRejectedExc("Authorization failed.") - self.auth_tries += 1 - raise AuthRequiredExc(atype='Basic', realm=self.realm) - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/openerp/service/wsgi_server.py b/openerp/service/wsgi_server.py index b31736ba998..5e72ba570b5 100644 --- a/openerp/service/wsgi_server.py +++ b/openerp/service/wsgi_server.py @@ -164,153 +164,6 @@ def wsgi_xmlrpc(environ, start_response): params, method = xmlrpclib.loads(data) return xmlrpc_return(start_response, service, method, params, string_faultcode) -def wsgi_webdav(environ, start_response): - pi = environ['PATH_INFO'] - if environ['REQUEST_METHOD'] == 'OPTIONS' and pi in ['*','/']: - return return_options(environ, start_response) - elif pi.startswith('/webdav'): - http_dir = websrv_lib.find_http_service(pi) - if http_dir: - path = pi[len(http_dir.path):] - if path.startswith('/'): - environ['PATH_INFO'] = path - else: - environ['PATH_INFO'] = '/' + path - return http_to_wsgi(http_dir)(environ, start_response) - -def return_options(environ, start_response): - # Microsoft specific header, see - # http://www.ibm.com/developerworks/rational/library/2089.html - if 'Microsoft' in environ.get('User-Agent', ''): - options = [('MS-Author-Via', 'DAV')] - else: - options = [] - options += [('DAV', '1 2'), ('Allow', 'GET HEAD PROPFIND OPTIONS REPORT')] - start_response("200 OK", [('Content-Length', str(0))] + options) - return [] - -def http_to_wsgi(http_dir): - """ - Turn a BaseHTTPRequestHandler into a WSGI entry point. - - Actually the argument is not a bare BaseHTTPRequestHandler but is wrapped - (as a class, so it needs to be instanciated) in a HTTPDir. - - This code is adapted from wbsrv_lib.MultiHTTPHandler._handle_one_foreign(). - It is a temporary solution: the HTTP sub-handlers (in particular the - document_webdav addon) have to be WSGIfied. - """ - def wsgi_handler(environ, start_response): - - headers = {} - for key, value in environ.items(): - if key.startswith('HTTP_'): - key = key[5:].replace('_', '-').title() - headers[key] = value - if key == 'CONTENT_LENGTH': - key = key.replace('_', '-').title() - headers[key] = value - if environ.get('Content-Type'): - headers['Content-Type'] = environ['Content-Type'] - - path = urllib.quote(environ.get('PATH_INFO', '')) - if environ.get('QUERY_STRING'): - path += '?' + environ['QUERY_STRING'] - - request_version = 'HTTP/1.1' # TODO - request_line = "%s %s %s\n" % (environ['REQUEST_METHOD'], path, request_version) - - class Dummy(object): - pass - - # Let's pretend we have a server to hand to the handler. - server = Dummy() - server.server_name = environ['SERVER_NAME'] - server.server_port = int(environ['SERVER_PORT']) - - # Initialize the underlying handler and associated auth. provider. - con = openerp.service.websrv_lib.noconnection(environ['wsgi.input']) - handler = http_dir.instanciate_handler(con, environ['REMOTE_ADDR'], server) - - # Populate the handler as if it is called by a regular HTTP server - # and the request is already parsed. - handler.wfile = StringIO.StringIO() - handler.rfile = environ['wsgi.input'] - handler.headers = headers - handler.command = environ['REQUEST_METHOD'] - handler.path = path - handler.request_version = request_version - handler.close_connection = 1 - handler.raw_requestline = request_line - handler.requestline = request_line - - # Handle authentication if there is an auth. provider associated to - # the handler. - if hasattr(handler, 'auth_provider'): - try: - handler.auth_provider.checkRequest(handler, path) - except websrv_lib.AuthRequiredExc, ae: - # Darwin 9.x.x webdav clients will report "HTTP/1.0" to us, while they support (and need) the - # authorisation features of HTTP/1.1 - if request_version != 'HTTP/1.1' and ('Darwin/9.' not in handler.headers.get('User-Agent', '')): - start_response("403 Forbidden", []) - return [] - start_response("401 Authorization required", [ - ('WWW-Authenticate', '%s realm="%s"' % (ae.atype,ae.realm)), - # ('Connection', 'keep-alive'), - ('Content-Type', 'text/html'), - ('Content-Length', 4), # len(self.auth_required_msg) - ]) - return ['Blah'] # self.auth_required_msg - except websrv_lib.AuthRejectedExc,e: - start_response("403 %s" % (e.args[0],), []) - return [] - - method_name = 'do_' + handler.command - - # Support the OPTIONS method even when not provided directly by the - # handler. TODO I would prefer to remove it and fix the handler if - # needed. - if not hasattr(handler, method_name): - if handler.command == 'OPTIONS': - return return_options(environ, start_response) - start_response("501 Unsupported method (%r)" % handler.command, []) - return [] - - # Finally, call the handler's method. - try: - method = getattr(handler, method_name) - method() - # The DAV handler buffers its output and provides a _flush() - # method. - getattr(handler, '_flush', lambda: None)() - response = parse_http_response(handler.wfile.getvalue()) - response_headers = response.getheaders() - body = response.read() - start_response(str(response.status) + ' ' + response.reason, response_headers) - return [body] - except (websrv_lib.AuthRejectedExc, websrv_lib.AuthRequiredExc): - raise - except Exception, e: - start_response("500 Internal error", []) - return [] - - return wsgi_handler - -def parse_http_response(s): - """ Turn a HTTP response string into a httplib.HTTPResponse object.""" - class DummySocket(StringIO.StringIO): - """ - This is used to provide a StringIO to httplib.HTTPResponse - which, instead of taking a file object, expects a socket and - uses its makefile() method. - """ - def makefile(self, *args, **kw): - return self - response = httplib.HTTPResponse(DummySocket(s)) - response.begin() - return response - # WSGI handlers registered through the register_wsgi_handler() function below. module_handlers = [] # RPC endpoints registered through the register_rpc_endpoint() function below. @@ -342,7 +195,7 @@ def application_unproxied(environ, start_response): del threading.current_thread().dbname # Try all handlers until one returns some result (i.e. not None). - wsgi_handlers = [wsgi_xmlrpc, wsgi_webdav] + wsgi_handlers = [wsgi_xmlrpc] wsgi_handlers += module_handlers for handler in wsgi_handlers: result = handler(environ, start_response) diff --git a/openerp/tools/config.py b/openerp/tools/config.py index f20cd65bc42..6b21bda8b4f 100644 --- a/openerp/tools/config.py +++ b/openerp/tools/config.py @@ -152,19 +152,11 @@ class configmanager(object): parser.add_option_group(group) # WEB - # TODO move to web addons after MetaOption merge group = optparse.OptionGroup(parser, "Web interface Configuration") group.add_option("--db-filter", dest="dbfilter", default='.*', help="Filter listed database", metavar="REGEXP") parser.add_option_group(group) - # Static HTTP - group = optparse.OptionGroup(parser, "Static HTTP service") - group.add_option("--static-http-enable", dest="static_http_enable", action="store_true", my_default=False, help="enable static HTTP service for serving plain HTML files") - group.add_option("--static-http-document-root", dest="static_http_document_root", help="specify the directory containing your static HTML files (e.g '/var/www/')") - group.add_option("--static-http-url-prefix", dest="static_http_url_prefix", help="specify the URL root prefix where you want web browsers to access your static HTML files (e.g '/')") - parser.add_option_group(group) - # Testing Group group = optparse.OptionGroup(parser, "Testing Configuration") group.add_option("--test-file", dest="test_file", my_default=False, @@ -394,7 +386,6 @@ class configmanager(object): 'db_maxconn', 'import_partial', 'addons_path', 'xmlrpc', 'syslog', 'without_demo', 'timezone', 'xmlrpcs_interface', 'xmlrpcs_port', 'xmlrpcs', - 'static_http_enable', 'static_http_document_root', 'static_http_url_prefix', 'secure_cert_file', 'secure_pkey_file', 'dbfilter', 'log_handler', 'log_level', 'log_db' ]