diff options
Diffstat (limited to 'addons/document_webdav/webdav_server.py')
| -rw-r--r-- | addons/document_webdav/webdav_server.py | 676 |
1 files changed, 0 insertions, 676 deletions
diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py deleted file mode 100644 index e495f5c9f2f..00000000000 --- a/addons/document_webdav/webdav_server.py +++ /dev/null @@ -1,676 +0,0 @@ -# -*- encoding: utf-8 -*- -############################################################################9 -# -# Copyright P. Christeas <p_christ@hol.gr> 2008-2010 -# Copyright OpenERP SA, 2010 (http://www.openerp.com ) -# -# Disclaimer: Many of the functions below borrow code from the -# python-webdav library (http://code.google.com/p/pywebdav/ ), -# which they import and override to suit OpenERP functionality. -# python-webdav was written by: Simon Pamies <s.pamies@banality.de> -# Christian Scholz <mrtopf@webdav.de> -# Vince Spicer <vince@vince.ca> -# -# 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 -# -# 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 3 -# 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 -############################################################################### - - -import logging -import openerp -from dav_fs import openerp_dav_handler -from openerp.tools.config import config -try: - from pywebdav.lib.WebDAVServer import DAVRequestHandler - from pywebdav.lib.utils import IfParser, TagList - from pywebdav.lib.errors import DAV_Error, DAV_Forbidden, DAV_NotFound - from pywebdav.lib.propfind import PROPFIND -except ImportError: - from DAV.WebDAVServer import DAVRequestHandler - from DAV.utils import IfParser, TagList - from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound - from DAV.propfind import PROPFIND -from openerp.service import http_server -from openerp.service.websrv_lib import FixSendError, HttpOptions -from BaseHTTPServer import BaseHTTPRequestHandler -import urlparse -import urllib -import re -import time -from string import atoi -import socket -# from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 -from xml.dom import minidom -from redirect import RedirectHTTPHandler -_logger = logging.getLogger(__name__) -khtml_re = re.compile(r' KHTML/([0-9\.]+) ') - -def OpenDAVConfig(**kw): - class OpenDAV: - def __init__(self, **kw): - self.__dict__.update(**kw) - - def getboolean(self, word): - return self.__dict__.get(word, False) - - class Config: - DAV = OpenDAV(**kw) - - return Config() - - -class DAVHandler(DAVRequestHandler, HttpOptions, FixSendError): - verbose = False - - protocol_version = 'HTTP/1.1' - _HTTP_OPTIONS= { 'DAV' : ['1', '2'], - 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', - 'PROPFIND', 'PROPPATCH', 'OPTIONS', 'MKCOL', - 'DELETE', 'TRACE', 'REPORT', ] - } - - def __init__(self, request, client_address, server): - self.request = request - self.client_address = client_address - self.server = server - self.setup() - - def get_userinfo(self, user, pw): - return False - - def _log(self, message): - self._logger.debug(message) - - def handle(self): - """Handle multiple requests if necessary.""" - self.close_connection = 1 - try: - self.handle_one_request() - while not self.close_connection: - self.handle_one_request() - except Exception as e: - try: - self.log_error("Request timed out: %r \n Trying old version of HTTPServer", e) - self._init_buffer() - except Exception as e: - #a read or a write timed out. Discard this connection - self.log_error("Not working neither, closing connection\n %r", e) - self.close_connection = 1 - - def finish(self): - pass - - def get_db_from_path(self, uri): - # interface class will handle all cases. - res = self.IFACE_CLASS.get_db(uri, allow_last=True) - return res - - def setup(self): - self.davpath = '/'+config.get_misc('webdav','vdir','webdav') - addr, port = self.server.server_name, self.server.server_port - server_proto = getattr(self.server,'proto', 'http').lower() - # Too early here to use self.headers - self.baseuri = "%s://%s:%d/"% (server_proto, addr, port) - self.IFACE_CLASS = openerp_dav_handler(self, self.verbose) - - def copymove(self, CLASS): - """ Our uri scheme removes the /webdav/ component from there, so we - need to mangle the header, too. - """ - up = urlparse.urlparse(urllib.unquote(self.headers['Destination'])) - if up.path.startswith(self.davpath): - self.headers['Destination'] = up.path[len(self.davpath):] - else: - raise DAV_Forbidden("Not allowed to copy/move outside webdav path.") - # TODO: locks - DAVRequestHandler.copymove(self, CLASS) - - def get_davpath(self): - return self.davpath - - def log_message(self, format, *args): - _logger.debug(format % args) - - def log_error(self, format, *args): - _logger.warning(format % args) - - def _prep_OPTIONS(self, opts): - ret = opts - dc=self.IFACE_CLASS - uri=urlparse.urljoin(self.get_baseuri(dc), self.path) - uri=urllib.unquote(uri) - try: - ret = dc.prep_http_options(uri, opts) - except DAV_Error, (ec,dd): - pass - except Exception,e: - self.log_error("Error at options: %s", str(e)) - raise - return ret - - def send_response(self, code, message=None): - # the BufferingHttpServer will send Connection: close , while - # the BaseHTTPRequestHandler will only accept int code. - # workaround both of them. - if self.command == 'PROPFIND' and int(code) == 404: - kh = khtml_re.search(self.headers.get('User-Agent','')) - if kh and (kh.group(1) < '4.5'): - # There is an ugly bug in all khtml < 4.5.x, where the 404 - # response is treated as an immediate error, which would even - # break the flow of a subsequent PUT request. At the same time, - # the 200 response (rather than 207 with content) is treated - # as "path not exist", so we send this instead - # https://bugs.kde.org/show_bug.cgi?id=166081 - code = 200 - BaseHTTPRequestHandler.send_response(self, int(code), message) - - def send_header(self, key, value): - if key == 'Connection' and value == 'close': - self.close_connection = 1 - DAVRequestHandler.send_header(self, key, value) - - def send_body(self, DATA, code=None, msg=None, desc=None, ctype='application/octet-stream', headers=None): - if headers and 'Connection' in headers: - pass - elif self.request_version in ('HTTP/1.0', 'HTTP/0.9'): - pass - elif self.close_connection == 1: # close header already sent - pass - elif headers and self.headers.get('Connection',False) == 'Keep-Alive': - headers['Connection'] = 'keep-alive' - - if headers is None: - headers = {} - - DAVRequestHandler.send_body(self, DATA, code=code, msg=msg, desc=desc, - ctype=ctype, headers=headers) - - def do_PUT(self): - dc=self.IFACE_CLASS - uri=urlparse.urljoin(self.get_baseuri(dc), self.path) - uri=urllib.unquote(uri) - # Handle If-Match - if self.headers.has_key('If-Match'): - test = False - etag = None - - for match in self.headers['If-Match'].split(','): - if match == '*': - if dc.exists(uri): - test = True - break - else: - if dc.match_prop(uri, match, "DAV:", "getetag"): - test = True - break - if not test: - self._get_body() - self.send_status(412) - return - - # Handle If-None-Match - if self.headers.has_key('If-None-Match'): - test = True - etag = None - for match in self.headers['If-None-Match'].split(','): - if match == '*': - if dc.exists(uri): - test = False - break - else: - if dc.match_prop(uri, match, "DAV:", "getetag"): - test = False - break - if not test: - self._get_body() - self.send_status(412) - return - - # Handle expect - expect = self.headers.get('Expect', '') - if (expect.lower() == '100-continue' and - self.protocol_version >= 'HTTP/1.1' and - self.request_version >= 'HTTP/1.1'): - self.send_status(100) - self._flush() - - # read the body - body=self._get_body() - - # locked resources are not allowed to be overwritten - if self._l_isLocked(uri): - return self.send_body(None, '423', 'Locked', 'Locked') - - ct=None - if self.headers.has_key("Content-Type"): - ct=self.headers['Content-Type'] - try: - location = dc.put(uri, body, ct) - except DAV_Error, (ec,dd): - self.log_error("Cannot PUT to %s: %s", uri, dd) - return self.send_status(ec) - - headers = {} - etag = None - if location and isinstance(location, tuple): - etag = location[1] - location = location[0] - # note that we have allowed for > 2 elems - if location: - headers['Location'] = location - else: - try: - if not etag: - etag = dc.get_prop(location or uri, "DAV:", "getetag") - if etag: - headers['ETag'] = str(etag) - except Exception: - pass - - self.send_body(None, '201', 'Created', '', headers=headers) - - def _get_body(self): - body = None - if self.headers.has_key("Content-Length"): - l=self.headers['Content-Length'] - body=self.rfile.read(atoi(l)) - return body - - def do_DELETE(self): - try: - DAVRequestHandler.do_DELETE(self) - except DAV_Error, (ec, dd): - return self.send_status(ec) - - def do_UNLOCK(self): - """ Unlocks given resource """ - - dc = self.IFACE_CLASS - self.log_message('UNLOCKing resource %s' % self.headers) - - uri = urlparse.urljoin(self.get_baseuri(dc), self.path) - uri = urllib.unquote(uri) - - token = self.headers.get('Lock-Token', False) - if token: - token = token.strip() - if token[0] == '<' and token[-1] == '>': - token = token[1:-1] - else: - token = False - - if not token: - return self.send_status(400, 'Bad lock token') - - try: - res = dc.unlock(uri, token) - except DAV_Error, (ec, dd): - return self.send_status(ec, dd) - - if res == True: - self.send_body(None, '204', 'OK', 'Resource unlocked.') - else: - # We just differentiate the description, for debugging purposes - self.send_body(None, '204', 'OK', 'Resource not locked.') - - def do_LOCK(self): - """ Attempt to place a lock on the given resource. - """ - - dc = self.IFACE_CLASS - lock_data = {} - - self.log_message('LOCKing resource %s' % self.headers) - - body = None - if self.headers.has_key('Content-Length'): - l = self.headers['Content-Length'] - body = self.rfile.read(atoi(l)) - - depth = self.headers.get('Depth', 'infinity') - - uri = urlparse.urljoin(self.get_baseuri(dc), self.path) - uri = urllib.unquote(uri) - self.log_message('do_LOCK: uri = %s' % uri) - - ifheader = self.headers.get('If') - - if ifheader: - ldif = IfParser(ifheader) - if isinstance(ldif, list): - if len(ldif) !=1 or (not isinstance(ldif[0], TagList)) \ - or len(ldif[0].list) != 1: - raise DAV_Error(400, "Cannot accept multiple tokens.") - ldif = ldif[0].list[0] - if ldif[0] == '<' and ldif[-1] == '>': - ldif = ldif[1:-1] - - lock_data['token'] = ldif - - if not body: - lock_data['refresh'] = True - else: - lock_data['refresh'] = False - lock_data.update(self._lock_unlock_parse(body)) - - if lock_data['refresh'] and not lock_data.get('token', False): - raise DAV_Error(400, 'Lock refresh must specify token.') - - lock_data['depth'] = depth - - try: - created, data, lock_token = dc.lock(uri, lock_data) - except DAV_Error, (ec, dd): - return self.send_status(ec, dd) - - headers = {} - if not lock_data['refresh']: - headers['Lock-Token'] = '<%s>' % lock_token - - if created: - self.send_body(data, '201', 'Created', ctype='text/xml', headers=headers) - else: - self.send_body(data, '200', 'OK', ctype='text/xml', headers=headers) - - def _lock_unlock_parse(self, body): - # Override the python-webdav function, with some improvements - # Unlike the py-webdav one, we also parse the owner minidom elements into - # pure pythonic struct. - doc = minidom.parseString(body) - - data = {} - owners = [] - for info in doc.getElementsByTagNameNS('DAV:', 'lockinfo'): - for scope in info.getElementsByTagNameNS('DAV:', 'lockscope'): - for scc in scope.childNodes: - if scc.nodeType == info.ELEMENT_NODE \ - and scc.namespaceURI == 'DAV:': - data['lockscope'] = scc.localName - break - for ltype in info.getElementsByTagNameNS('DAV:', 'locktype'): - for ltc in ltype.childNodes: - if ltc.nodeType == info.ELEMENT_NODE \ - and ltc.namespaceURI == 'DAV:': - data['locktype'] = ltc.localName - break - for own in info.getElementsByTagNameNS('DAV:', 'owner'): - for ono in own.childNodes: - if ono.nodeType == info.TEXT_NODE: - if ono.data: - owners.append(ono.data) - elif ono.nodeType == info.ELEMENT_NODE \ - and ono.namespaceURI == 'DAV:' \ - and ono.localName == 'href': - href = '' - for hno in ono.childNodes: - if hno.nodeType == info.TEXT_NODE: - href += hno.data - owners.append(('href','DAV:', href)) - - if len(owners) == 1: - data['lockowner'] = owners[0] - elif not owners: - pass - else: - data['lockowner'] = owners - return data - -from openerp.service.http_server import reg_http_service,OpenERPAuthProvider - -class DAVAuthProvider(OpenERPAuthProvider): - def authenticate(self, db, user, passwd, client_address): - """ authenticate, but also allow the False db, meaning to skip - authentication when no db is specified. - """ - if db is False: - return True - return OpenERPAuthProvider.authenticate(self, db, user, passwd, client_address) - - -class dummy_dav_interface(object): - """ Dummy dav interface """ - verbose = True - - PROPS={"DAV:" : ('creationdate', - 'displayname', - 'getlastmodified', - 'resourcetype', - ), - } - - M_NS={"DAV:" : "_get_dav", } - - def __init__(self, parent): - self.parent = parent - - def get_propnames(self, uri): - return self.PROPS - - def get_prop(self, uri, ns, propname): - if self.M_NS.has_key(ns): - prefix=self.M_NS[ns] - else: - raise DAV_NotFound - mname=prefix+"_"+propname.replace('-', '_') - try: - m=getattr(self,mname) - r=m(uri) - return r - except AttributeError: - raise DAV_NotFound - - def get_data(self, uri, range=None): - raise DAV_NotFound - - def _get_dav_creationdate(self, uri): - return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()) - - def _get_dav_getlastmodified(self, uri): - return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) - - def _get_dav_displayname(self, uri): - return uri - - def _get_dav_resourcetype(self, uri): - return ('collection', 'DAV:') - - def exists(self, uri): - """ return 1 or None depending on if a resource exists """ - uri2 = uri.split('/') - if len(uri2) < 3: - return True - _logger.debug("Requested uri: %s", uri) - return None # no - - def is_collection(self, uri): - """ return 1 or None depending on if a resource is a collection """ - return None # no - -class DAVStaticHandler(http_server.StaticHTTPHandler): - """ A variant of the Static handler, which will serve dummy DAV requests - """ - - verbose = False - protocol_version = 'HTTP/1.1' - _HTTP_OPTIONS= { 'DAV' : ['1', '2'], - 'Allow' : [ 'GET', 'HEAD', - 'PROPFIND', 'OPTIONS', 'REPORT', ] - } - - def send_body(self, content, code, message='OK', content_type='text/xml'): - self.send_response(int(code), message) - self.send_header("Content-Type", content_type) - # self.send_header('Connection', 'close') - self.send_header('Content-Length', len(content) or 0) - self.end_headers() - if hasattr(self, '_flush'): - self._flush() - - if self.command != 'HEAD': - self.wfile.write(content) - - def do_PROPFIND(self): - """Answer to PROPFIND with generic data. - - A rough copy of python-webdav's do_PROPFIND, but hacked to work - statically. - """ - - dc = dummy_dav_interface(self) - - # read the body containing the xml request - # iff there is no body then this is an ALLPROP request - body = None - if self.headers.has_key('Content-Length'): - l = self.headers['Content-Length'] - body = self.rfile.read(atoi(l)) - - path = self.path.rstrip('/') - uri = urllib.unquote(path) - - pf = PROPFIND(uri, dc, self.headers.get('Depth', 'infinity'), body) - - try: - DATA = '%s\n' % pf.createResponse() - except DAV_Error, (ec,dd): - return self.send_error(ec,dd) - except Exception: - self.log_exception("Cannot PROPFIND") - raise - - # work around MSIE DAV bug for creation and modified date - # taken from Resource.py @ Zope webdav - if (self.headers.get('User-Agent') == - 'Microsoft Data Access Internet Publishing Provider DAV 1.1'): - DATA = DATA.replace('<ns0:getlastmodified xmlns:ns0="DAV:">', - '<ns0:getlastmodified xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.rfc1123">') - DATA = DATA.replace('<ns0:creationdate xmlns:ns0="DAV:">', - '<ns0:creationdate xmlns:n="DAV:" xmlns:b="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/" b:dt="dateTime.tz">') - - self.send_body(DATA, '207','Multi-Status','Multiple responses') - - def not_get_baseuri(self): - baseuri = '/' - if self.headers.has_key('Host'): - uparts = list(urlparse.urlparse('/')) - uparts[1] = self.headers['Host'] - baseuri = urlparse.urlunparse(uparts) - return baseuri - - def get_davpath(self): - return '' - - -try: - - if (config.get_misc('webdav','enable',True)): - directory = '/'+config.get_misc('webdav','vdir','webdav') - handler = DAVHandler - verbose = config.get_misc('webdav','verbose',True) - handler.debug = config.get_misc('webdav','debug',True) - _dc = { 'verbose' : verbose, - 'directory' : directory, - 'lockemulation' : True, - } - - conf = OpenDAVConfig(**_dc) - handler._config = conf - reg_http_service(directory, DAVHandler, DAVAuthProvider) - _logger.info("WebDAV service registered at path: %s/ "% directory) - - if not (config.get_misc('webdav', 'no_root_hack', False)): - # Now, replace the static http handler with the dav-enabled one. - # If a static-http service has been specified for our server, then - # read its configuration and use that dir_path. - # NOTE: this will _break_ any other service that would be registered - # at the root path in future. - base_path = False - if config.get_misc('static-http','enable', False): - base_path = config.get_misc('static-http', 'base_path', '/') - if base_path and base_path == '/': - dir_path = config.get_misc('static-http', 'dir_path', False) - else: - dir_path = openerp.modules.module.get_module_resource('document_webdav','public_html') - # an _ugly_ hack: we put that dir back in tools.config.misc, so that - # the StaticHttpHandler can find its dir_path. - config.misc.setdefault('static-http',{})['dir_path'] = dir_path - - reg_http_service('/', DAVStaticHandler) - -except Exception, e: - _logger.error('Cannot launch webdav: %s' % e) - - -def init_well_known(): - reps = RedirectHTTPHandler.redirect_paths - - num_svcs = config.get_misc('http-well-known', 'num_services', '0') - - for nsv in range(1, int(num_svcs)+1): - uri = config.get_misc('http-well-known', 'service_%d' % nsv, False) - path = config.get_misc('http-well-known', 'path_%d' % nsv, False) - if not (uri and path): - continue - reps['/'+uri] = path - - if int(num_svcs): - reg_http_service('/.well-known', RedirectHTTPHandler) - -init_well_known() - -class PrincipalsRedirect(RedirectHTTPHandler): - - - redirect_paths = {} - - def _find_redirect(self): - for b, r in self.redirect_paths.items(): - if self.path.startswith(b): - return r + self.path[len(b):] - return False - -def init_principals_redirect(): - """ Some devices like the iPhone will look under /principals/users/xxx for - the user's properties. In OpenERP we _cannot_ have a stray /principals/... - working path, since we have a database path and the /webdav/ component. So, - the best solution is to redirect the url with 301. Luckily, it does work in - the device. The trick is that we need to hard-code the database to use, either - the one centrally defined in the config, or a "forced" one in the webdav - section. - """ - dbname = config.get_misc('webdav', 'principal_dbname', False) - if (not dbname) and not config.get_misc('webdav', 'no_principals_redirect', False): - dbname = config.get('db_name', False) - if dbname: - PrincipalsRedirect.redirect_paths[''] = '/webdav/%s/principals' % dbname - reg_http_service('/principals', PrincipalsRedirect) - _logger.info( - "Registered HTTP redirect handler for /principals to the %s db.", - dbname) - -init_principals_redirect() - -#eof - - - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
