diff options
| author | Antony Lesuisse <al@openerp.com> | 2014-03-26 14:01:46 +0100 |
|---|---|---|
| committer | Antony Lesuisse <al@openerp.com> | 2014-03-26 14:01:46 +0100 |
| commit | 502d6770152e13c7560ea3adb25dddd4265dc016 (patch) | |
| tree | d932cc192d7a1d915e647848e59d127e9b660d84 /addons/document_webdav/test_davclient.py | |
| parent | 158b6554c8c283a210c66fed06c6ecaf4e1a16f0 (diff) | |
[REM] move document_webdav to openerp-extra
bzr revid: al@openerp.com-20140326130146-up8geys9qzh4jhc8
Diffstat (limited to 'addons/document_webdav/test_davclient.py')
| -rw-r--r-- | addons/document_webdav/test_davclient.py | 707 |
1 files changed, 0 insertions, 707 deletions
diff --git a/addons/document_webdav/test_davclient.py b/addons/document_webdav/test_davclient.py deleted file mode 100644 index f3a8b35b582..00000000000 --- a/addons/document_webdav/test_davclient.py +++ /dev/null @@ -1,707 +0,0 @@ -# -*- encoding: utf-8 -*- -# -# Copyright P. Christeas <p_christ@hol.gr> 2008,2009 -# Copyright OpenERP SA. (http://www.openerp.com) 2010 -# -# -# 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 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 -############################################################################### - -""" A trivial HTTP/WebDAV client, used for testing the server -""" -# code taken from the 'http-client.py' script: -# http://git.hellug.gr/?p=xrg/openerp;a=history;f=tests/http-client.py;hb=refs/heads/xrg-60 - -import gzip -import logging -import xml.dom.minidom - -import httplib - -from openerp.tools import config -from xmlrpclib import Transport, ProtocolError -import StringIO -import base64 -from openerp import SUPERUSER_ID - -_logger = logging.getLogger(__name__) - -class HTTP11(httplib.HTTP): - _http_vsn = 11 - _http_vsn_str = 'HTTP/1.1' - -class PersistentTransport(Transport): - """Handles an HTTP transaction to an XML-RPC server, persistently.""" - - def __init__(self, use_datetime=0): - self._use_datetime = use_datetime - self._http = {} - log.debug("Using persistent transport") - - def make_connection(self, host): - # create a HTTP connection object from a host descriptor - if not self._http.has_key(host): - host, extra_headers, x509 = self.get_host_info(host) - self._http[host] = HTTP11(host) - _logger.debug("New connection to %s", host) - return self._http[host] - - def get_host_info(self, host): - host, extra_headers, x509 = Transport.get_host_info(self,host) - if extra_headers == None: - extra_headers = [] - - extra_headers.append( ( 'Connection', 'keep-alive' )) - - return host, extra_headers, x509 - - def _parse_response(self, file, sock, response): - """ read response from input file/socket, and parse it - We are persistent, so it is important to only parse - the right amount of input - """ - - p, u = self.getparser() - - if response.msg.get('content-encoding') == 'gzip': - gzdata = StringIO.StringIO() - while not response.isclosed(): - rdata = response.read(1024) - if not rdata: - break - gzdata.write(rdata) - gzdata.seek(0) - rbuffer = gzip.GzipFile(mode='rb', fileobj=gzdata) - while True: - respdata = rbuffer.read() - if not respdata: - break - p.feed(respdata) - else: - while not response.isclosed(): - rdata = response.read(1024) - if not rdata: - break - p.feed(rdata) - if len(rdata)<1024: - break - - p.close() - return u.close() - - def request(self, host, handler, request_body, verbose=0): - # issue XML-RPC request - - h = self.make_connection(host) - if verbose: - h.set_debuglevel(1) - - self.send_request(h, handler, request_body) - self.send_host(h, host) - self.send_user_agent(h) - self.send_content(h, request_body) - - resp = h._conn.getresponse() - # TODO: except BadStatusLine, e: - - errcode, errmsg, headers = resp.status, resp.reason, resp.msg - - - if errcode != 200: - raise ProtocolError( - host + handler, - errcode, errmsg, - headers - ) - - self.verbose = verbose - - try: - sock = h._conn.sock - except AttributeError: - sock = None - - return self._parse_response(h.getfile(), sock, resp) - -class CompressedTransport(PersistentTransport): - def send_content(self, connection, request_body): - connection.putheader("Content-Type", "text/xml") - - if len(request_body) > 512 or True: - buffer = StringIO.StringIO() - output = gzip.GzipFile(mode='wb', fileobj=buffer) - output.write(request_body) - output.close() - buffer.seek(0) - request_body = buffer.getvalue() - connection.putheader('Content-Encoding', 'gzip') - - connection.putheader("Content-Length", str(len(request_body))) - connection.putheader("Accept-Encoding",'gzip') - connection.endheaders() - if request_body: - connection.send(request_body) - - def send_request(self, connection, handler, request_body): - connection.putrequest("POST", handler, skip_accept_encoding=1) - -class SafePersistentTransport(PersistentTransport): - def make_connection(self, host): - # create a HTTP connection object from a host descriptor - if not self._http.has_key(host): - host, extra_headers, x509 = self.get_host_info(host) - self._http[host] = httplib.HTTPS(host, None, **(x509 or {})) - _logger.debug("New connection to %s", host) - return self._http[host] - -class AuthClient(object): - def getAuth(self, atype, realm): - raise NotImplementedError("Cannot authenticate for %s" % atype) - - def resolveFailedRealm(self, realm): - """ Called when, using a known auth type, the realm is not in cache - """ - raise NotImplementedError("Cannot authenticate for realm %s" % realm) - -class BasicAuthClient(AuthClient): - def __init__(self): - self._realm_dict = {} - - def getAuth(self, atype, realm): - if atype != 'Basic' : - return super(BasicAuthClient,self).getAuth(atype, realm) - - if not self._realm_dict.has_key(realm): - _logger.debug("realm dict: %r", self._realm_dict) - _logger.debug("missing key: \"%s\"" % realm) - self.resolveFailedRealm(realm) - return 'Basic '+ self._realm_dict[realm] - - def addLogin(self, realm, username, passwd): - """ Add some known username/password for a specific login. - This function should be called once, for each realm - that we want to authenticate against - """ - assert realm - auths = base64.encodestring(username + ':' + passwd) - if auths[-1] == "\n": - auths = auths[:-1] - self._realm_dict[realm] = auths - -class addAuthTransport: - """ Intermediate class that authentication algorithm to http transport - """ - - def setAuthClient(self, authobj): - """ Set the authentication client object. - This method must be called before any request is issued, that - would require http authentication - """ - assert isinstance(authobj, AuthClient) - self._auth_client = authobj - - - def request(self, host, handler, request_body, verbose=0): - # issue XML-RPC request - - h = self.make_connection(host) - if verbose: - h.set_debuglevel(1) - - tries = 0 - atype = None - realm = None - - while(tries < 3): - self.send_request(h, handler, request_body) - self.send_host(h, host) - self.send_user_agent(h) - if atype: - # This line will bork if self.setAuthClient has not - # been issued. That is a programming error, fix your code! - auths = self._auth_client.getAuth(atype, realm) - _logger.debug("sending authorization: %s", auths) - h.putheader('Authorization', auths) - self.send_content(h, request_body) - - resp = h._conn.getresponse() - # except BadStatusLine, e: - tries += 1 - - if resp.status == 401: - if 'www-authenticate' in resp.msg: - (atype,realm) = resp.msg.getheader('www-authenticate').split(' ',1) - data1 = resp.read() - if data1: - log.warning("Why have data on a 401 auth. message?") - if realm.startswith('realm="') and realm.endswith('"'): - realm = realm[7:-1] - _logger.debug("Resp: %r %r", resp.version,resp.isclosed(), resp.will_close) - _logger.debug("Want to do auth %s for realm %s", atype, realm) - if atype != 'Basic': - raise ProtocolError(host+handler, 403, - "Unknown authentication method: %s" % atype, resp.msg) - continue # with the outer while loop - else: - raise ProtocolError(host+handler, 403, - 'Server-incomplete authentication', resp.msg) - - if resp.status != 200: - raise ProtocolError( host + handler, - resp.status, resp.reason, resp.msg ) - - self.verbose = verbose - - try: - sock = h._conn.sock - except AttributeError: - sock = None - - return self._parse_response(h.getfile(), sock, resp) - - raise ProtocolError(host+handler, 403, "No authentication.",'') - -class PersistentAuthTransport(addAuthTransport,PersistentTransport): - pass - -class PersistentAuthCTransport(addAuthTransport,CompressedTransport): - pass - -class HTTPSConnection(httplib.HTTPSConnection): - certs_file = None - def connect(self): - "Connect to a host on a given (SSL) port. check the certificate" - import socket, ssl - - if HTTPSConnection.certs_file: - ca_certs = HTTPSConnection.certs_file - cert_reqs = ssl.CERT_REQUIRED - else: - ca_certs = None - cert_reqs = ssl.CERT_NONE - sock = socket.create_connection((self.host, self.port), self.timeout) - self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file, - ca_certs=ca_certs, - cert_reqs=cert_reqs) - - - def getpeercert(self): - import ssl - cert = None - if self.sock: - cert = self.sock.getpeercert() - else: - cert = ssl.get_server_certificate((self.host,self.port), - ssl_version=ssl.PROTOCOL_SSLv23 ) - lf = (len(ssl.PEM_FOOTER)+1) - if cert[0-lf] != '\n': - cert = cert[:0-lf]+'\n'+cert[0-lf:] - _logger.debug("len-footer: %s cert: %r", lf, cert[0-lf]) - - return cert - - -class DAVClient(object): - """An instance of a WebDAV client, connected to the OpenERP server - """ - - def __init__(self, user=None, passwd=None, dbg=0, use_ssl=False, useragent=False, timeout=None): - if use_ssl: - self.host = config.get_misc('httpsd', 'interface', False) - self.port = config.get_misc('httpsd', 'port', 8071) - if not self.host: - self.host = config.get('xmlrpcs_interface') - self.port = config.get('xmlrpcs_port') - else: - self.host = config.get_misc('httpd', 'interface') - self.port = config.get_misc('httpd', 'port', 8069) - if not self.host: - self.host = config.get('xmlrpc_interface') - self.port = config.get('xmlrpc_port') or self.port - if self.host == '0.0.0.0' or not self.host: - self.host = '127.0.0.1' - self.port = int(self.port) - if not config.get_misc('webdav','enable',True): - raise Exception("WebDAV is disabled, cannot continue") - self.davpath = '/' + config.get_misc('webdav','vdir','webdav') - self.user = user - self.passwd = passwd - self.dbg = dbg - self.timeout = timeout or 5.0 # seconds, tests need to respond pretty fast! - self.hdrs = {} - if useragent: - self.set_useragent(useragent) - - def get_creds(self, obj, cr, uid): - """Read back the user credentials from cr, uid - - @param obj is any orm object, in order to use its pool - @param uid is the numeric id, which we will try to reverse resolve - - note: this is a hackish way to get the credentials. It is expected - to break if "base_crypt" is used. - """ - ruob = obj.pool.get('res.users') - res = ruob.read(cr, SUPERUSER_ID, [uid,], ['login', 'password']) - assert res, "uid %s not found" % uid - self.user = res[0]['login'] - self.passwd = res[0]['password'] - if self.passwd.startswith('$1$'): - # md5 by base crypt. We cannot decode, wild guess - # that passwd = login - self.passwd = self.user - return True - - def set_useragent(self, uastr): - """ Set the user-agent header to something meaningful. - Some shorthand names will be replaced by stock strings. - """ - if uastr in ('KDE4', 'Korganizer'): - self.hdrs['User-Agent'] = "Mozilla/5.0 (compatible; Konqueror/4.4; Linux) KHTML/4.4.3 (like Gecko)" - elif uastr == 'iPhone3': - self.hdrs['User-Agent'] = "DAVKit/5.0 (765); iCalendar/5.0 (79); iPhone/4.1 8B117" - elif uastr == "MacOS": - self.hdrs['User-Agent'] = "WebDAVFS/1.8 (01808000) Darwin/9.8.0 (i386)" - else: - self.hdrs['User-Agent'] = uastr - - def _http_request(self, path, method='GET', hdrs=None, body=None): - if not hdrs: - hdrs = {} - import base64 - dbg = self.dbg - hdrs.update(self.hdrs) - _logger.debug("Getting %s http://%s:%d/%s", method, self.host, self.port, path) - conn = httplib.HTTPConnection(self.host, port=self.port, timeout=self.timeout) - conn.set_debuglevel(dbg) - if not path: - path = "/index.html" - if not hdrs.has_key('Connection'): - hdrs['Connection']= 'keep-alive' - conn.request(method, path, body, hdrs ) - try: - r1 = conn.getresponse() - except httplib.BadStatusLine, bsl: - log.warning("Bad status line: %s", bsl.line) - raise Exception('Bad status line.') - if r1.status == 401: # and r1.headers: - if 'www-authenticate' in r1.msg: - (atype,realm) = r1.msg.getheader('www-authenticate').split(' ',1) - data1 = r1.read() - if not self.user: - raise Exception('Must auth, have no user/pass!') - _logger.debug("Ver: %s, closed: %s, will close: %s", r1.version,r1.isclosed(), r1.will_close) - _logger.debug("Want to do auth %s for realm %s", atype, realm) - if atype == 'Basic' : - auths = base64.encodestring(self.user + ':' + self.passwd) - if auths[-1] == "\n": - auths = auths[:-1] - hdrs['Authorization']= 'Basic '+ auths - #sleep(1) - conn.request(method, path, body, hdrs ) - r1 = conn.getresponse() - else: - raise Exception("Unknown auth type %s" %atype) - else: - _logger.warning("Got 401, cannot auth") - raise Exception('No auth') - - _logger.debug("Reponse: %s %s",r1.status, r1.reason) - data1 = r1.read() - if method != 'GET': - _logger.debug("Body:\n%s\nEnd of body", data1) - try: - ctype = r1.msg.getheader('content-type') - if ctype and ';' in ctype: - ctype, encoding = ctype.split(';',1) - if ctype == 'text/xml': - doc = xml.dom.minidom.parseString(data1) - _logger.debug("XML Body:\n %s", doc.toprettyxml(indent="\t")) - except Exception: - _logger.warning("Cannot print XML.", exc_info=True) - pass - conn.close() - return r1.status, r1.msg, data1 - - def _assert_headers(self, expect, msg): - """ Assert that the headers in msg contain the expect values - """ - for k, v in expect.items(): - hval = msg.getheader(k) - if not hval: - raise AssertionError("Header %s not defined in http response" % k) - if isinstance(v, (list, tuple)): - delim = ',' - hits = map(str.strip, hval.split(delim)) - mvits= [] - for vit in v: - if vit not in hits: - mvits.append(vit) - if mvits: - raise AssertionError("HTTP header \"%s\" is missing: %s" %(k, ', '.join(mvits))) - else: - if hval.strip() != v.strip(): - raise AssertionError("HTTP header \"%s: %s\"" % (k, hval)) - - def gd_options(self, path='*', expect=None): - """ Test the http options functionality - If a dictionary is defined in expect, those options are - asserted. - """ - if path != '*': - path = self.davpath + path - hdrs = { 'Content-Length': 0 - } - s, m, d = self._http_request(path, method='OPTIONS', hdrs=hdrs) - assert s == 200, "Status: %r" % s - assert 'OPTIONS' in m.getheader('Allow') - _logger.debug('Options: %r', m.getheader('Allow')) - - if expect: - self._assert_headers(expect, m) - - def _parse_prop_response(self, data): - """ Parse a propfind/propname response - """ - def getText(node): - rc = [] - for node in node.childNodes: - if node.nodeType == node.TEXT_NODE: - rc.append(node.data) - return ''.join(rc) - - def getElements(node, namespaces=None, strict=False): - for cnod in node.childNodes: - if cnod.nodeType != node.ELEMENT_NODE: - if strict: - _logger.debug("Found %r inside <%s>", cnod, node.tagName) - continue - if namespaces and (cnod.namespaceURI not in namespaces): - _logger.debug("Ignoring <%s> in <%s>", cnod.tagName, node.localName) - continue - yield cnod - - nod = xml.dom.minidom.parseString(data) - nod_r = nod.documentElement - res = {} - assert nod_r.localName == 'multistatus', nod_r.tagName - for resp in nod_r.getElementsByTagNameNS('DAV:', 'response'): - href = None - status = 200 - res_nss = {} - for cno in getElements(resp, namespaces=['DAV:',]): - if cno.localName == 'href': - assert href is None, "Second href in same response" - href = getText(cno) - elif cno.localName == 'propstat': - for pno in getElements(cno, namespaces=['DAV:',]): - rstatus = None - if pno.localName == 'prop': - for prop in getElements(pno): - key = prop.localName - tval = getText(prop).strip() - val = tval or (True, rstatus or status) - if prop.namespaceURI == 'DAV:' and prop.localName == 'resourcetype': - val = 'plain' - for rte in getElements(prop, namespaces=['DAV:',]): - # Note: we only look at DAV:... elements, we - # actually expect only one DAV:collection child - val = rte.localName - res_nss.setdefault(prop.namespaceURI,{})[key] = val - elif pno.localName == 'status': - rstr = getText(pno) - htver, sta, msg = rstr.split(' ', 3) - assert htver == 'HTTP/1.1' - rstatus = int(sta) - else: - _logger.debug("What is <%s> inside a <propstat>?", pno.tagName) - - else: - _logger.debug("Unknown node: %s", cno.tagName) - - res.setdefault(href,[]).append((status, res_nss)) - - return res - - def gd_propfind(self, path, props=None, depth=0): - if not props: - propstr = '<allprop/>' - else: - propstr = '<prop>' - nscount = 0 - for p in props: - ns = None - if isinstance(p, tuple): - p, ns = p - if ns is None or ns == 'DAV:': - propstr += '<%s/>' % p - else: - propstr += '<ns%d:%s xmlns:ns%d="%s" />' %(nscount, p, nscount, ns) - nscount += 1 - propstr += '</prop>' - - body="""<?xml version="1.0" encoding="utf-8"?> - <propfind xmlns="DAV:">%s</propfind>""" % propstr - hdrs = { 'Content-Type': 'text/xml; charset=utf-8', - 'Accept': 'text/xml', - 'Depth': depth, - } - - s, m, d = self._http_request(self.davpath + path, method='PROPFIND', - hdrs=hdrs, body=body) - assert s == 207, "Bad status: %s" % s - ctype = m.getheader('Content-Type').split(';',1)[0] - assert ctype == 'text/xml', m.getheader('Content-Type') - res = self._parse_prop_response(d) - if depth == 0: - assert len(res) == 1 - res = res.values()[0] - else: - assert len(res) >= 1 - return res - - - def gd_propname(self, path, depth=0): - body="""<?xml version="1.0" encoding="utf-8"?> - <propfind xmlns="DAV:"><propname/></propfind>""" - hdrs = { 'Content-Type': 'text/xml; charset=utf-8', - 'Accept': 'text/xml', - 'Depth': depth - } - s, m, d = self._http_request(self.davpath + path, method='PROPFIND', - hdrs=hdrs, body=body) - assert s == 207, "Bad status: %s" % s - ctype = m.getheader('Content-Type').split(';',1)[0] - assert ctype == 'text/xml', m.getheader('Content-Type') - res = self._parse_prop_response(d) - if depth == 0: - assert len(res) == 1 - res = res.values()[0] - else: - assert len(res) >= 1 - return res - - def gd_getetag(self, path, depth=0): - return self.gd_propfind(path, props=['getetag',], depth=depth) - - def gd_lsl(self, path): - """ Return a list of 'ls -l' kind of data for a folder - - This is based on propfind. - """ - - lspairs = [ ('name', 'displayname', 'n/a'), ('size', 'getcontentlength', '0'), - ('type', 'resourcetype', '----------'), ('uid', 'owner', 'nobody'), - ('gid', 'group', 'nogroup'), ('mtime', 'getlastmodified', 'n/a'), - ('mime', 'getcontenttype', 'application/data'), ] - - propnames = [ l[1] for l in lspairs] - propres = self.gd_propfind(path, props=propnames, depth=1) - - res = [] - for href, pr in propres.items(): - lsline = {} - for st, nsdic in pr: - davprops = nsdic['DAV:'] - if st == 200: - for lsp in lspairs: - if lsp[1] in davprops: - if lsp[1] == 'resourcetype': - if davprops[lsp[1]] == 'collection': - lsline[lsp[0]] = 'dr-xr-x---' - else: - lsline[lsp[0]] = '-r-xr-x---' - else: - lsline[lsp[0]] = davprops[lsp[1]] - elif st in (404, 403): - for lsp in lspairs: - if lsp[1] in davprops: - lsline[lsp[0]] = lsp[2] - else: - _logger.debug("Strange status: %s", st) - - res.append(lsline) - - return res - - def gd_get(self, path, crange=None, mime=None, compare=None): - """ HTTP GET for path, supporting Partial ranges - """ - hdrs = { 'Accept': mime or '*/*', } - if crange: - if isinstance(crange, tuple): - crange = [crange,] - if not isinstance(crange, list): - raise TypeError("Range must be a tuple or list of tuples.") - rs = [] - for r in crange: - rs.append('%d-%d' % r) - hdrs['Range'] = 'bytes='+ (','.join(rs)) - s, m, d = self._http_request(self.davpath + path, method='GET', hdrs=hdrs) - assert s in (200, 206), "Bad status: %s" % s - ctype = m.getheader('Content-Type') - if ctype and ';' in ctype: - ctype = ctype.split(';',1)[0] - if mime: - assert ctype == mime, m.getheader('Content-Type') - rrange = None - rrh = m.getheader('Content-Range') - if rrh: - assert rrh.startswith('bytes '), rrh - rrh=rrh[6:].split('/',1)[0] - rrange = map(int, rrh.split('-',1)) - if compare: - # we need to compare the returned data with that of compare - fd = open(compare, 'rb') - d2 = fd.read() - fd.close() - if crange: - if len(crange) > 1: - raise NotImplementedError - r = crange[0] - d2 = d2[r[0]:r[1]+1] - assert d2 == d, "Data does not match" - return ctype, rrange, d - - def gd_put(self, path, body=None, srcpath=None, mime=None, noclobber=False): - """ HTTP PUT - @param noclobber will prevent overwritting a resource (If-None-Match) - @param mime will set the content-type - """ - hdrs = { } - if not (body or srcpath): - raise ValueError("PUT must have something to send.") - if (not body) and srcpath: - fd = open(srcpath, 'rb') - body = fd.read() - fd.close() - if mime: - hdrs['Content-Type'] = mime - if noclobber: - hdrs['If-None-Match'] = '*' - s, m, d = self._http_request(self.davpath + path, method='PUT', - hdrs=hdrs, body=body) - assert s == (201), "Bad status: %s" % s - etag = m.getheader('ETag') - return etag or True - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
