diff --git a/addons/base_iban/base_iban.py b/addons/base_iban/base_iban.py index 6f633c7b474..f473bd090c0 100644 --- a/addons/base_iban/base_iban.py +++ b/addons/base_iban/base_iban.py @@ -117,7 +117,7 @@ class res_partner_bank(osv.osv): iban_country = self.browse(cr, uid, ids)[0].iban[:2] if default_iban_check(iban_country): iban_example = iban_country in _ref_iban and _ref_iban[iban_country] + ' \nWhere A = Account number, B = National bank code, S = Branch code, C = account No, N = branch No, K = National check digits....' or '' - return _('The IBAN does not seems to be correct. You should have entered something like this %s'), (iban_example) + return _('The IBAN does not seem to be correct. You should have entered something like this %s'), (iban_example) return _('The IBAN is invalid, It should begin with the country code'), () def name_get(self, cr, uid, ids, context=None): diff --git a/addons/document/document.py b/addons/document/document.py index dd34dd4295c..79fb83b5b1f 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -165,6 +165,15 @@ class document_file(osv.osv): return False return True + def check(self, cr, uid, ids, mode, context=None, values=None): + """Check access wrt. res_model, relax the rule of ir.attachment parent + + With 'document' installed, everybody will have access to attachments of + any resources they can *read*. + """ + return super(document_file, self).check(cr, uid, ids, mode='read', + context=context, values=values) + def copy(self, cr, uid, id, default=None, context=None): if not default: default = {} diff --git a/addons/document/wizard/document_configuration.py b/addons/document/wizard/document_configuration.py index 26ce8652599..40dc2b3afd8 100644 --- a/addons/document/wizard/document_configuration.py +++ b/addons/document/wizard/document_configuration.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- ############################################################################## -# +# # OpenERP, Open Source Management Solution # Copyright (C) 2004-2010 Tiny SPRL (). # @@ -15,22 +15,24 @@ # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . +# along with this program. If not, see . # ############################################################################## + from osv import osv, fields + class document_configuration(osv.osv_memory): _name='document.configuration' _description = 'Auto Directory Configuration' - _inherit = 'res.config' + _inherit = 'res.config' _columns = { 'sale_order' : fields.boolean('Sale Order', help="Auto directory configuration for Sale Orders and Quotation with report."), 'product' : fields.boolean('Product', help="Auto directory configuration for Products."), 'project': fields.boolean('Project', help="Auto directory configuration for Projects."), } - + def execute(self, cr, uid, ids, context=None): conf_id = ids and ids[0] or False @@ -58,7 +60,7 @@ class document_configuration(osv.osv_memory): quta_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id else: quta_dir_id = data_pool.create(cr, uid, {'name': 'Sale Quotations'}) - + dir_pool.write(cr, uid, [quta_dir_id], { 'type':'ressource', 'ressource_type_id': mid[0], @@ -86,7 +88,7 @@ class document_configuration(osv.osv_memory): 'include_name': 1, 'directory_id': quta_dir_id, }) - + if conf.product and self.pool.get('product.product'): # Product @@ -95,12 +97,12 @@ class document_configuration(osv.osv_memory): product_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id else: product_dir_id = data_pool.create(cr, uid, {'name': 'Products'}) - + mid = model_pool.search(cr, uid, [('model','=','product.product')]) dir_pool.write(cr, uid, [product_dir_id], { 'type':'ressource', 'ressource_type_id': mid[0], - }) + }) if conf.project and self.pool.get('account.analytic.account'): # Project @@ -109,12 +111,12 @@ class document_configuration(osv.osv_memory): project_dir_id = data_pool.browse(cr, uid, dir_data_id, context=context).res_id else: project_dir_id = data_pool.create(cr, uid, {'name': 'Projects'}) - + mid = model_pool.search(cr, uid, [('model','=','account.analytic.account')]) dir_pool.write(cr, uid, [project_dir_id], { 'type':'ressource', 'ressource_type_id': mid[0], 'domain': '[]', 'ressource_tree': 1 - }) + }) document_configuration() diff --git a/addons/document/wizard/document_configuration_view.xml b/addons/document/wizard/document_configuration_view.xml index d922a4bcc1c..156af432b0e 100644 --- a/addons/document/wizard/document_configuration_view.xml +++ b/addons/document/wizard/document_configuration_view.xml @@ -45,6 +45,8 @@ + + diff --git a/addons/document_ftp/wizard/ftp_browse.py b/addons/document_ftp/wizard/ftp_browse.py index 3e5787819b2..08d3db01703 100644 --- a/addons/document_ftp/wizard/ftp_browse.py +++ b/addons/document_ftp/wizard/ftp_browse.py @@ -43,9 +43,11 @@ class document_ftp_browse(osv.osv_memory): url = ftp_url.url and ftp_url.url.split('ftp://') or [] if url: url = url[1] + if url[-1] == '/': + url = url[:-1] else: url = '%s:%s' %(ftpserver.HOST, ftpserver.PORT) - res['url'] = 'ftp://%s@%s'%(current_user.login, url) + res['url'] = 'ftp://%s@%s/%s'%(current_user.login, url, cr.dbname) return res def browse_ftp(self, cr, uid, ids, context=None): diff --git a/addons/document_webdav/__openerp__.py b/addons/document_webdav/__openerp__.py index 6c1e24d1e96..0fd21a41a17 100644 --- a/addons/document_webdav/__openerp__.py +++ b/addons/document_webdav/__openerp__.py @@ -30,7 +30,7 @@ { "name" : "WebDAV server for Document Management", - "version" : "2.2", + "version" : "2.3", "author" : "OpenERP SA", "category" : "Generic Modules/Others", "website": "http://www.openerp.com", @@ -49,6 +49,9 @@ ; since the messages are routed to the python logging, with ; levels "debug" and "debug_rpc" respectively, you can leave ; these options on + + Also implements IETF RFC 5785 for services discovery on a http server, + which needs explicit configuration in openerp-server.conf, too. """, "depends" : ["base", "document"], "init_xml" : [], diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index ab7b1dbf5db..924e489ccc4 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -686,7 +686,7 @@ class openerp_dav_handler(dav_interface): except Exception: node = False - objname = uri2[-1] + objname = misc.ustr(uri2[-1]) ret = None if not node: @@ -719,7 +719,7 @@ class openerp_dav_handler(dav_interface): etag = str(newchild.get_etag(cr)) except Exception, e: self.parent.log_error("Cannot get etag for node: %s" % e) - ret = (hurl, etag) + ret = (str(hurl), etag) else: self._try_function(node.set_data, (cr, data), "save %s" % objname, cr=cr) diff --git a/addons/document_webdav/doc/well-known.rst b/addons/document_webdav/doc/well-known.rst new file mode 100644 index 00000000000..ce7dbd7c9cf --- /dev/null +++ b/addons/document_webdav/doc/well-known.rst @@ -0,0 +1,29 @@ +================= +Well-known URIs +================= + +In accordance to IETF RFC 5785 [1], we shall publish a few locations +on the root of our http server, so that clients can discover our +services (CalDAV, eg.). + +This module merely installs a special http request handler, that will +redirect the URIs from "http://our-server:port/.well-known/xxx' to +the correct path for each xxx service. + +Note that well-known URIs cannot have a database-specific behaviour, +they are server-wide. So, we have to explicitly chose one of our databases +to serve at them. By default, the database of the configuration file +is chosen + +Example config: + +[http-well-known] +num_services = 2 +db_name = openerp-main ; must define that for path_1 below +service_1 = caldav +path_1 = /webdav/%(db_name)s/Calendars/ +service_2 = foo +path_2 = /other_db/static/Foo.html + + +[1] http://www.rfc-editor.org/rfc/rfc5785.txt \ No newline at end of file diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index fc48a47056a..5927eade899 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -299,7 +299,7 @@ class node_file(node_acl_mixin, nodes.node_file): return '' def get_dav_props(self, cr): - return self._get_dav_props_hlpr(cr, nodes.node_dir, + return self._get_dav_props_hlpr(cr, nodes.node_file, 'document.webdav.file.property', 'file_id', self.file_id) def dav_lock(self, cr, lock_data): @@ -345,11 +345,11 @@ class node_database(nodes.node_database): return ('collection', 'DAV:') def get_dav_props(self, cr): - return self._get_dav_props_hlpr(cr, nodes.node_dir, + return self._get_dav_props_hlpr(cr, nodes.node_database, 'document.webdav.dir.property', 'dir_id', False) def get_dav_eprop(self, cr, ns, prop): - return self._get_dav_eprop_hlpr(cr, nodes.node_dir, ns, prop, + return self._get_dav_eprop_hlpr(cr, nodes.node_database, ns, prop, 'document.webdav.dir.property', 'dir_id', False) class node_res_obj(node_acl_mixin, nodes.node_res_obj): diff --git a/addons/document_webdav/public_html/index.html b/addons/document_webdav/public_html/index.html new file mode 100644 index 00000000000..b3a841aced7 --- /dev/null +++ b/addons/document_webdav/public_html/index.html @@ -0,0 +1,9 @@ + + +OpenERP server + + +This is an OpenERP server. Nothing to GET here. + + + diff --git a/addons/document_webdav/redirect.py b/addons/document_webdav/redirect.py new file mode 100644 index 00000000000..71af3271ab7 --- /dev/null +++ b/addons/document_webdav/redirect.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2010 OpenERP s.a. (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero 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 Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +import logging +import urlparse +from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions +from service.http_server import HttpLogHandler + +class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler): + _logger = logging.getLogger('httpd.well-known') + _HTTP_OPTIONS = { 'Allow': ['OPTIONS', 'GET', 'HEAD', 'PROPFIND'] } + redirect_paths = {} + + def __init__(self,request, client_address, server): + HTTPHandler.__init__(self,request,client_address,server) + + def send_head(self): + """Common code for GET and HEAD commands. + + It will either send the correct redirect (Location) response + or a 404. + """ + + if self.path.endswith('/'): + self.path = self.path[:-1] + + if not self.path: + # Return an empty page + self.send_response(200) + self.send_header("Content-Length", 0) + self.end_headers() + return None + + redir_path = self._find_redirect() + if redir_path is False: + self.send_error(404, "File not found") + return None + elif redir_path is None: + return None + + server_proto = getattr(self.server, 'proto', 'http').lower() + addr, port = self.server.server_name, self.server.server_port + try: + addr, port = self.request.getsockname() + except Exception, e: + self.log_error("Cannot calculate own address:" , e) + + if self.headers.has_key('Host'): + uparts = list(urlparse.urlparse("%s://%s:%d"% (server_proto, addr,port))) + uparts[1] = self.headers['Host'] + baseuri = urlparse.urlunparse(uparts) + else: + baseuri = "%s://%s:%d"% (server_proto, addr, port ) + + + location = baseuri + redir_path + # relative uri: location = self.redirect_paths[self.path] + + self.send_response(301) + self.send_header("Location", location) + self.send_header("Content-Length", 0) + self.end_headers() + # Do we need a Cache-content: header here? + self._logger.debug("redirecting %s to %s", self.path, redir_path) + return None + + def do_PROPFIND(self): + self._get_ignore_body() + return self.do_HEAD() + + def _find_redirect(self): + """ Locate self.path among the redirects we can handle + @return The new path, False for 404 or None for already sent error + """ + return self.redirect_paths.get(self.path, False) + + def _get_ignore_body(self): + if not self.headers.has_key("content-length"): + return + max_chunk_size = 10*1024*1024 + size_remaining = int(self.headers["content-length"]) + got = '' + while size_remaining: + chunk_size = min(size_remaining, max_chunk_size) + got = self.rfile.read(chunk_size) + size_remaining -= len(got) + +#eof + diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index dd19aeef7d5..1b60ade698f 100644 --- a/addons/document_webdav/webdav.py +++ b/addons/document_webdav/webdav.py @@ -45,13 +45,103 @@ class Text2(xml.dom.minidom.Text): data = data.replace(">", ">") writer.write(data) -def createText2Node(doc, data): - if not isinstance(data, StringTypes): - raise TypeError, "node contents must be a string" - t = Text2() - t.data = data - t.ownerDocument = doc - return t +class Prop2xml(object): + """ A helper class to convert property structs to DAV:XML + + Written to generalize the use of _prop_child(), a class is + needed to hold some persistent data accross the recursions + of _prop_elem_child(). + """ + + def __init__(self, doc, namespaces, nsnum): + """ Init the structure + @param doc the xml doc element + @param namespaces a dict of namespaces + @param nsnum the next namespace number to define + """ + self.doc = doc + self.namespaces = namespaces + self.nsnum = nsnum + + def createText2Node(self, data): + if not isinstance(data, StringTypes): + raise TypeError, "node contents must be a string" + t = Text2() + t.data = data + t.ownerDocument = self.doc + return t + + def _prop_child(self, xnode, ns, prop, value): + """Append a property xml node to xnode, with value + + And a little smarter than that, it will consider namespace and + also allow nested properties etc. + + :param ns the namespace of the node + :param prop the name of the property + :param value the value. Can be: + string: text node + tuple ('elem', 'ns') for empty sub-node + tuple ('elem', 'ns', sub-elems) for sub-node with elements + tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with + optional elements and attributes + list, of above tuples + """ + if ns == 'DAV:': + ns_prefix = 'D:' + else: + ns_prefix="ns"+str(self.namespaces.index(ns))+":" + + pe = self.doc.createElement(ns_prefix+str(prop)) + if hasattr(value, '__class__') and value.__class__.__name__ == 'Element': + pe.appendChild(value) + else: + if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int): + # hack, to go.. + if value == 1: + ve = self.doc.createElement("D:collection") + pe.appendChild(ve) + else: + self._prop_elem_child(pe, ns, value, ns_prefix) + + xnode.appendChild(pe) + + def _prop_elem_child(self, pnode, pns, v, pns_prefix): + + if isinstance(v, list): + for vit in v: + self._prop_elem_child(pnode, pns, vit, pns_prefix) + elif isinstance(v,tuple): + need_ns = False + if v[1] == pns: + ns_prefix = pns_prefix + elif v[1] == 'DAV:': + ns_prefix = 'D:' + elif v[1] in self.namespaces: + ns_prefix="ns"+str(self.namespaces.index(v[1]))+":" + else: + ns_prefix="ns"+str(self.nsnum)+":" + need_ns = True + + ve = self.doc.createElement(ns_prefix+v[0]) + if need_ns: + ve.setAttribute("xmlns:ns"+str(self.nsnum), v[1]) + if len(v) > 2 and v[2] is not None: + if isinstance(v[2], (list, tuple)): + # support nested elements like: + # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] + self._prop_elem_child(ve, v[1], v[2], ns_prefix) + else: + vt = self.createText2Node(tools.ustr(v[2])) + ve.appendChild(vt) + if len(v) > 3 and v[3]: + assert isinstance(v[3], dict) + for ak, av in v[3].items(): + ve.setAttribute(ak, av) + pnode.appendChild(ve) + else: + ve = self.createText2Node(tools.ustr(v)) + pnode.appendChild(ve) super_mk_prop_response = PROPFIND.mk_prop_response @@ -73,78 +163,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): re.setAttribute("xmlns:ns"+str(nsnum),nsname) nsnum=nsnum+1 - def _prop_child(xnode, ns, prop, value): - """Append a property xml node to xnode, with value - - And a little smarter than that, it will consider namespace and - also allow nested properties etc. - - :param ns the namespace of the node - :param prop the name of the property - :param value the value. Can be: - string: text node - tuple ('elem', 'ns') for empty sub-node - tuple ('elem', 'ns', sub-elems) for sub-node with elements - tuple ('elem', 'ns', sub-elems, {attrs}) for sub-node with - optional elements and attributes - list, of above tuples - """ - if ns == 'DAV:': - ns_prefix = 'D:' - else: - ns_prefix="ns"+str(namespaces.index(ns))+":" - - pe=doc.createElement(ns_prefix+str(prop)) - if hasattr(value, '__class__') and value.__class__.__name__ == 'Element': - pe.appendChild(value) - else: - if ns == 'DAV:' and prop=="resourcetype" and isinstance(value, int): - # hack, to go.. - if value == 1: - ve=doc.createElement("D:collection") - pe.appendChild(ve) - else: - _prop_elem_child(pe, ns, value, ns_prefix) - - xnode.appendChild(pe) - - def _prop_elem_child(pnode, pns, v, pns_prefix): - - if isinstance(v, list): - for vit in v: - _prop_elem_child(pnode, pns, vit, pns_prefix) - elif isinstance(v,tuple): - need_ns = False - if v[1] == pns: - ns_prefix = pns_prefix - elif v[1] == 'DAV:': - ns_prefix = 'D:' - elif v[1] in namespaces: - ns_prefix="ns"+str(namespaces.index(v[1]))+":" - else: - ns_prefix="ns"+str(nsnum)+":" - need_ns = True - - ve=doc.createElement(ns_prefix+v[0]) - if need_ns: - ve.setAttribute("xmlns:ns"+str(nsnum), v[1]) - if len(v) > 2 and v[2] is not None: - if isinstance(v[2], (list, tuple)): - # support nested elements like: - # ( 'elem', 'ns:', [('sub-elem1', 'ns1'), ...] - _prop_elem_child(ve, v[1], v[2], ns_prefix) - else: - vt=createText2Node(doc,tools.ustr(v[2])) - ve.appendChild(vt) - if len(v) > 3 and v[3]: - assert isinstance(v[3], dict) - for ak, av in v[3].items(): - ve.setAttribute(ak, av) - pnode.appendChild(ve) - else: - ve=createText2Node(doc, tools.ustr(v)) - pnode.appendChild(ve) - + propgen = Prop2xml(doc, namespaces, nsnum) # write href information uparts=urlparse.urlparse(uri) fileloc=uparts[2] @@ -180,7 +199,7 @@ def mk_prop_response(self, uri, good_props, bad_props, doc): for p,v in good_props[ns].items(): if v is None: continue - _prop_child(gp, ns, p, v) + propgen._prop_child(gp, ns, p, v) ps.appendChild(gp) re.appendChild(ps) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 5b0b418e9a1..e23daab9f66 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -1,7 +1,8 @@ # -*- encoding: utf-8 -*- - +############################################################################9 # # Copyright P. Christeas 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/ ), @@ -19,7 +20,7 @@ # # 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 +# 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, @@ -33,20 +34,26 @@ ############################################################################### +import logging import netsvc from dav_fs import openerp_dav_handler from tools.config import config from DAV.WebDAVServer import DAVRequestHandler +from service import http_server from service.websrv_lib import HTTPDir, FixSendError, HttpOptions from BaseHTTPServer import BaseHTTPRequestHandler import urlparse import urllib import re +import time from string import atoi -from DAV.errors import * +import addons from DAV.utils import IfParser, TagList +from DAV.errors import DAV_Error, DAV_Forbidden, DAV_NotFound +from DAV.propfind import PROPFIND # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 from xml.dom import minidom +from redirect import RedirectHTTPHandler khtml_re = re.compile(r' KHTML/([0-9\.]+) ') @@ -66,6 +73,7 @@ def OpenDAVConfig(**kw): class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): verbose = False + _logger = logging.getLogger('webdav') protocol_version = 'HTTP/1.1' _HTTP_OPTIONS= { 'DAV' : ['1', '2'], 'Allow' : [ 'GET', 'HEAD', 'COPY', 'MOVE', 'POST', 'PUT', @@ -75,8 +83,9 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): def get_userinfo(self,user,pw): return False + def _log(self, message): - netsvc.Logger().notifyChannel("webdav",netsvc.LOG_DEBUG,message) + self._logger.debug(message) def handle(self): self._init_buffer() @@ -118,10 +127,10 @@ class DAVHandler(HttpOptions, FixSendError, DAVRequestHandler): return self.davpath def log_message(self, format, *args): - netsvc.Logger().notifyChannel('webdav', netsvc.LOG_DEBUG_RPC, format % args) + self._logger.log(netsvc.logging.DEBUG_RPC,format % args) def log_error(self, format, *args): - netsvc.Logger().notifyChannel('xmlrpc', netsvc.LOG_WARNING, format % args) + self._logger.warning(format % args) def _prep_OPTIONS(self, opts): ret = opts @@ -415,6 +424,140 @@ class DAVAuthProvider(OpenERPAuthProvider): 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 + logging.getLogger('webdav').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('', + '') + DATA = DATA.replace('', + '') + + 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)): @@ -430,10 +573,80 @@ try: conf = OpenDAVConfig(**_dc) handler._config = conf reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider())) - netsvc.Logger().notifyChannel('webdav', netsvc.LOG_INFO, "WebDAV service registered at path: %s/ "% directory) + logging.getLogger('webdav').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 = addons.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 + + if reg_http_service(HTTPDir('/', DAVStaticHandler)): + logging.getLogger("web-services").info("WebDAV registered HTTP dir %s for /" % \ + (dir_path)) + except Exception, e: - logger = netsvc.Logger() - logger.notifyChannel('webdav', netsvc.LOG_ERROR, 'Cannot launch webdav: %s' % e) + logging.getLogger('webdav').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): + if http_server.reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)): + logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" ) + +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(HTTPDir('/principals', PrincipalsRedirect)) + logging.getLogger("web-services").info( + "Registered HTTP redirect handler for /principals to the %s db.", + dbname) + +init_principals_redirect() #eof diff --git a/addons/event_project/__init__.py b/addons/event_project/__init__.py index fe2b9771046..c43297c86f4 100644 --- a/addons/event_project/__init__.py +++ b/addons/event_project/__init__.py @@ -20,5 +20,6 @@ ############################################################################## import event_project import wizard + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/point_of_sale/pos_workflow.xml.orig b/addons/point_of_sale/pos_workflow.xml.orig deleted file mode 100644 index 16b9e116340..00000000000 --- a/addons/point_of_sale/pos_workflow.xml.orig +++ /dev/null @@ -1,238 +0,0 @@ - - - - - Pos workflow - pos.order - True - - - - - - POS - Confirmation - - - - - - - True - draft - - - - - payment - function - write({'state': 'payment'}) - - - - - rebate - function - write({'state': 'rebate'}) - - - - - unbalanced - function - write({'state': 'unbalanced'}) - - - - - cofinoga - function - write({'state': 'cofinoga'}) - - - - - collectivites - function - write({'state': 'collectivites'}) - - - - - cadeaux - function - write({'state': 'cadeaux'}) - - - - - collectivites - function - write({'state': 'collectivites', 'invoice_wanted': True}) - - - - - cadeaux - function - write({'state': 'cadeaux'}) - - - - - paid - action_paid() - function - - - - - done - True - action_done() - function - - - - - invoiced - True - action_invoice() - function - - - - - cancel - True - action_cancel() - function - - - - - - - - - start_payment - - - - - - test_paid() and not(test_rebate() or test_cofinoga() or test_cadeaux() or test_collectivites()) - payment - - - - - - test_rebate() - payment - - - - - - ok_rebate - - - - - - not test_paid() - payment - - - - - - test_paid() - - - - - - test_cofinoga() - payment - - - - - - ok_cofinoga - - - - - - test_collectivites() - payment - - - - - - ok_collectivites - - - - - - test_cadeaux() - payment - - - - - - ok_cadeaux - - - - - - test_collectivites() - payment - - - - - - ok_collectivites - - - - - - test_cadeaux() - payment - - - - - - ok_cadeaux - - - - - - done - - - - - - invoice - - - - - - cancel - - - - -