From 75c212285a3d9571f5222828aa99a41916aa6fda Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:51:44 +0200 Subject: [PATCH 01/21] email_template: cleanup the startup warning lines. Multiple coding malpractices (I can't name them errors, yet) were in those lines: - they used the netsvc.Logger() interface, which is deprecated - they wrapped too much (we're not COBOL) - they used capital letters for an object - they defined a logger object that would live throughout the module life, although it would only be useful for startup - they used the gettext function, although there is absolutely no context around to provide a language. bzr revid: p_christ@hol.gr-20101116145144-kwf7612bz0k1xzs2 --- addons/email_template/email_template.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/addons/email_template/email_template.py b/addons/email_template/email_template.py index 5db7538d952..c20f798fbbe 100644 --- a/addons/email_template/email_template.py +++ b/addons/email_template/email_template.py @@ -23,10 +23,9 @@ import base64 import random import netsvc +import logging import re -LOGGER = netsvc.Logger() - TEMPLATE_ENGINES = [] from osv import osv, fields @@ -35,12 +34,9 @@ from tools.translate import _ try: from mako.template import Template as MakoTemplate TEMPLATE_ENGINES.append(('mako', 'Mako Templates')) -except: - LOGGER.notifyChannel( - _("Email Template"), - netsvc.LOG_WARNING, - _("Mako templates not installed") - ) +except ImportError: + logging.getLogger('init').warning("module email_template: Mako templates not installed") + try: from django.template import Context, Template as DjangoTemplate #Workaround for bug: @@ -49,12 +45,8 @@ try: settings.configure() #Workaround ends TEMPLATE_ENGINES.append(('django', 'Django Template')) -except: - LOGGER.notifyChannel( - _("Email Template"), - netsvc.LOG_WARNING, - _("Django templates not installed") - ) +except ImportError: + logging.getLogger('init').warning("module email_template: Django templates not installed") import tools import pooler From 0fb8ef7e70942821a9697fbff40604186556cd90 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:52:07 +0200 Subject: [PATCH 02/21] account.move.line: Fix ugly SQL call A matter of coding style, tripple quotes in the same line, put in a list is just a candidate for the hall of shame. bzr revid: p_christ@hol.gr-20101116145207-t71p73sfzjyvsfht --- addons/account/account_move_line.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index cdb692f454a..304471964c7 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -300,19 +300,15 @@ class account_move_line(osv.osv): context = {} c = context.copy() c['initital_bal'] = True - sql = [ - """SELECT l2.id, SUM(l1.debit-l1.credit) FROM account_move_line l1, account_move_line l2""", - """WHERE l2.account_id = l1.account_id""", - """AND""", - """l1.id <= l2.id""", - """AND""", - """l2.id IN %s""", - """AND""", - self._query_get(cr, uid, obj='l1', context=c), - """ GROUP BY l2.id""", - ] + sql = """SELECT l2.id, SUM(l1.debit-l1.credit) + FROM account_move_line l1, account_move_line l2 + WHERE l2.account_id = l1.account_id + AND l1.id <= l2.id + AND l2.id IN %%s AND """ + \ + self._query_get(cr, uid, obj='l1', context=c) + \ + " GROUP BY l2.id" - cr.execute('\n'.join(sql), [tuple(ids)]) + cr.execute(sql, [tuple(ids)]) res = dict(cr.fetchall()) return res From cf7ffa4adc308d0c5bc0d60b96961219651f2306 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:52:21 +0200 Subject: [PATCH 03/21] board: fix a string typo It had been haunting our development process for years, making our teeth turn green and our cow's milk turn sour. bzr revid: p_christ@hol.gr-20101116145221-a9hw49ssn2rhpu24 --- addons/board/board.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/board/board.py b/addons/board/board.py index 9677c8aaa17..6049f501e00 100644 --- a/addons/board/board.py +++ b/addons/board/board.py @@ -185,7 +185,7 @@ class board_note_type(osv.osv): Board note Type """ _name = 'board.note.type' - _description = "NOte Type" + _description = "Note Type" _columns = { 'name': fields.char('Note Type', size=64, required=True), From 0c41f86c9ba2ce577d533d3b1d538f07f126edf7 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:54:59 +0200 Subject: [PATCH 04/21] base_iban: fix an English typo, that make our life miserable. bzr revid: p_christ@hol.gr-20101116145459-c13dzni8tnr02n8o --- addons/base_iban/base_iban.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/base_iban/base_iban.py b/addons/base_iban/base_iban.py index 92783b8eb74..4f56cfc7286 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): From e59e5eb818e729e975ac74670bb9a337f0b3927a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 16 Nov 2010 16:55:19 +0200 Subject: [PATCH 05/21] event_project: fix init.py after rename of event_project.py bzr revid: p_christ@hol.gr-20101116145519-km3q9g508wtc4mf6 --- addons/event_project/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/event_project/__init__.py b/addons/event_project/__init__.py index c629a522a8b..a1cee0e439d 100644 --- a/addons/event_project/__init__.py +++ b/addons/event_project/__init__.py @@ -18,7 +18,9 @@ # along with this program. If not, see . # ############################################################################## -import event + +import event_project import wizard + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 0848a8dc9664dd1a1e765bf89712e797be956428 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:42:57 +0200 Subject: [PATCH 06/21] Remove pos_workflow.xml.orig A file that was incorrectly committed in the first place. bzr revid: p_christ@hol.gr-20101123184257-t40kofpyohol51sw --- addons/point_of_sale/pos_workflow.xml.orig | 238 --------------------- 1 file changed, 238 deletions(-) delete mode 100644 addons/point_of_sale/pos_workflow.xml.orig 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 - - - - - From 213a206587059f59e6cbff6938e3c2fba1a9a2c8 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:43:16 +0200 Subject: [PATCH 07/21] users_ldap: replace tools.debug() with logging.debug() bzr revid: p_christ@hol.gr-20101123184316-x2chah3ijr97aab1 --- addons/users_ldap/users_ldap.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/addons/users_ldap/users_ldap.py b/addons/users_ldap/users_ldap.py index df4528107cc..bf59b1b62e9 100644 --- a/addons/users_ldap/users_ldap.py +++ b/addons/users_ldap/users_ldap.py @@ -21,6 +21,7 @@ from osv import fields, osv import pooler import tools +import logging from service import security import ldap from ldap.filter import filter_format @@ -66,9 +67,9 @@ class users(osv.osv): _inherit = "res.users" def login(self, db, login, password): ret = super(users,self).login(db, login, password) - tools.debug(ret) if ret: return ret + logger = logging.getLogger('orm.ldap') pool = pooler.get_pool(db) cr = pooler.get_db(db).cursor() action_obj = pool.get('ir.actions.actions') @@ -78,7 +79,7 @@ class users(osv.osv): FROM res_company_ldap WHERE ldap_server != '' and ldap_binddn != '' ORDER BY sequence""") for res_company_ldap in cr.dictfetchall(): - tools.debug(res_company_ldap) + logger.debug(res_company_ldap) try: l = ldap.open(res_company_ldap['ldap_server'], res_company_ldap['ldap_server_port']) if l.simple_bind_s(res_company_ldap['ldap_binddn'], res_company_ldap['ldap_password']): @@ -93,13 +94,13 @@ class users(osv.osv): continue if result_type == ldap.RES_SEARCH_RESULT and len(result_data) == 1: dn = result_data[0][0] - tools.debug(dn) + logger.debug(dn) name = result_data[0][1]['cn'][0] if l.bind_s(dn, password): l.unbind() cr.execute("SELECT id FROM res_users WHERE login=%s",(tools.ustr(login),)) res = cr.fetchone() - tools.debug(res) + logger.debug(res) if res: cr.close() return res[0] @@ -127,7 +128,7 @@ class users(osv.osv): return res l.unbind() except Exception, e: - tools.debug(e) + logger.warning("Cannot auth", exc_info=True) continue cr.close() return False @@ -135,10 +136,11 @@ class users(osv.osv): def check(self, db, uid, passwd): try: return super(users,self).check(db, uid, passwd) - except: # AccessDenied + except ExceptionNoTb: # AccessDenied pass cr = pooler.get_db(db).cursor() user = self.browse(cr, 1, uid) + logger = logging.getLogger('orm.ldap') if user and user.company_id.ldaps: for res_company_ldap in user.company_id.ldaps: try: @@ -161,7 +163,7 @@ class users(osv.osv): return True l.unbind() except Exception, e: - tools.debug(e) + logger.warning('cannot check', exc_info=True) pass cr.close() raise security.ExceptionNoTb('AccessDenied') From a7cc6e51a439584931bc234557d528e9ac59ac4a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:48:58 +0200 Subject: [PATCH 08/21] doc webdav: fix typos at get_dav_props bzr revid: p_christ@hol.gr-20101123184858-fdws4qtjfswiahra --- addons/document_webdav/nodes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/document_webdav/nodes.py b/addons/document_webdav/nodes.py index 8bea54ab7d3..fb4aef149a6 100644 --- a/addons/document_webdav/nodes.py +++ b/addons/document_webdav/nodes.py @@ -151,7 +151,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, None, 'file_id', self.file_id) #'document.webdav.dir.property', 'dir_id', self.dir_id) @@ -162,11 +162,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): From 91a0bbf1c53c7a3ae97ce6556029c2fbfcc168c0 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:49:05 +0200 Subject: [PATCH 09/21] doc webdav: Fix putting of utf-8 names We had a mix of unicode and utf-8 encoded strings, which broke the join at line 700. bzr revid: p_christ@hol.gr-20101123184905-9t7v1jrx257oog27 --- addons/document_webdav/dav_fs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/document_webdav/dav_fs.py b/addons/document_webdav/dav_fs.py index c34a323fadb..666ecb0133f 100644 --- a/addons/document_webdav/dav_fs.py +++ b/addons/document_webdav/dav_fs.py @@ -681,7 +681,7 @@ class openerp_dav_handler(dav_interface): except Exception: node = False - objname = uri2[-1] + objname = misc.ustr(uri2[-1]) ret = None if not node: @@ -714,7 +714,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) From 972d09e09d5a5654baf79a6b253fa5cfdc661c57 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:49:26 +0200 Subject: [PATCH 10/21] doc ftp: include the db name in the browse url. bzr revid: p_christ@hol.gr-20101123184926-8am5llm757m8uv4l --- addons/document_ftp/wizard/ftp_browse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/document_ftp/wizard/ftp_browse.py b/addons/document_ftp/wizard/ftp_browse.py index 42081c98fd0..24b7966f096 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): From 2aa5d026d1b2c562fef370c37604a7378788b4a3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:49:38 +0200 Subject: [PATCH 11/21] document: fix access of res.users folders We need to relax the rule of ir.attachment. Needs server patch, see commit message there. bzr revid: p_christ@hol.gr-20101123184938-jklijdqt0xx5u6cs --- addons/document/document.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/addons/document/document.py b/addons/document/document.py index e3d810668b4..790b650d4f5 100644 --- a/addons/document/document.py +++ b/addons/document/document.py @@ -133,6 +133,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 = {} From ecf7b56a86bd69d62387d3f83dafaddff512c318 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:50:11 +0200 Subject: [PATCH 12/21] doc webdav: install a dummy DAV handler at / , for Nautilus The gnome gvfs component falsely requires that / will serve PROPFIND requests. We reuse the static-http capability of the server (force activation rather than opt-in default behavior) and serve pseudo-DAV properties for the root. Conflicts: document_webdav/webdav_server.py bzr revid: p_christ@hol.gr-20101123185011-besih03q4gt2atps --- addons/document_webdav/public_html/index.html | 9 + addons/document_webdav/webdav_server.py | 164 +++++++++++++++++- 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 addons/document_webdav/public_html/index.html 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/webdav_server.py b/addons/document_webdav/webdav_server.py index 0e5973c95b3..5810978a6d5 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -2,6 +2,7 @@ # # Copyright P. Christeas 2008,2009 +# Copyright OpenERP SA, 2010 (http://www.openerp.com ) # # # WARNING: This program as such is intended to be used by professional @@ -27,17 +28,20 @@ ############################################################################### -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.errors import DAV_Error, DAV_Forbidden, DAV_NotFound +from DAV.propfind import PROPFIND # from DAV.constants import DAV_VERSION_1, DAV_VERSION_2 khtml_re = re.compile(r' KHTML/([0-9\.]+) ') @@ -273,6 +277,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)): @@ -289,6 +427,28 @@ try: handler._config = conf reg_http_service(HTTPDir(directory,DAVHandler,DAVAuthProvider())) netsvc.Logger().notifyChannel('webdav', netsvc.LOG_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) From 0d051581b4d947d4f28e5edf1245703237c7d09c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:51:47 +0200 Subject: [PATCH 13/21] document: bring back the configuration wizard Suggested by: FP bzr revid: p_christ@hol.gr-20101123185147-bp41deccy9n05vwo --- addons/document/__openerp__.py | 2 +- .../document/wizard/document_configuration.py | 22 ++++++++++--------- .../wizard/document_configuration_view.xml | 2 ++ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/addons/document/__openerp__.py b/addons/document/__openerp__.py index 5d0088579a1..8a2a6677cc0 100644 --- a/addons/document/__openerp__.py +++ b/addons/document/__openerp__.py @@ -46,7 +46,7 @@ 'security/document_security.xml', 'document_view.xml', 'document_data.xml', -# 'wizard/document_configuration_view.xml', + 'wizard/document_configuration_view.xml', 'security/ir.model.access.csv', 'report/document_report_view.xml', 'board_document_view.xml', diff --git a/addons/document/wizard/document_configuration.py b/addons/document/wizard/document_configuration.py index a497eb6af90..57dc8f5eff0 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 @@ + + From 5c6f3246970fc4d4791f9137064ecfba55ba2a19 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:52:44 +0200 Subject: [PATCH 14/21] http_well_known: new module, for RFC 5785 bzr revid: p_christ@hol.gr-20101123185244-6notdzskdw8jruau --- addons/http_well_known/__init__.py | 46 +++++++++++++++ addons/http_well_known/__openerp__.py | 44 +++++++++++++++ addons/http_well_known/doc/well-known.rst | 29 ++++++++++ addons/http_well_known/redirect.py | 69 +++++++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 addons/http_well_known/__init__.py create mode 100644 addons/http_well_known/__openerp__.py create mode 100644 addons/http_well_known/doc/well-known.rst create mode 100644 addons/http_well_known/redirect.py diff --git a/addons/http_well_known/__init__.py b/addons/http_well_known/__init__.py new file mode 100644 index 00000000000..45c4efee2e1 --- /dev/null +++ b/addons/http_well_known/__init__.py @@ -0,0 +1,46 @@ +# -*- 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 tools +from service.websrv_lib import HTTPDir +from service.http_server import reg_http_service +from redirect import RedirectHTTPHandler + +def init_well_known(): + reps = RedirectHTTPHandler.redirect_paths + + num_svcs = tools.config.get_misc('http-well-known', 'num_services', '0') + + for nsv in range(1, int(num_svcs)+1): + uri = tools.config.get_misc('http-well-known', 'service_%d' % nsv, False) + path = tools.config.get_misc('http-well-known', 'path_%d' % nsv, False) + if not (uri and path): + continue + reps['/'+uri] = path + + if reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)): + logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" ) + + +init_well_known() + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/http_well_known/__openerp__.py b/addons/http_well_known/__openerp__.py new file mode 100644 index 00000000000..090b9a5ee01 --- /dev/null +++ b/addons/http_well_known/__openerp__.py @@ -0,0 +1,44 @@ +# -*- 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 . +# +############################################################################## + + +{ + "name" : "Publish well-known http URIs", + "version" : "0.1", + "depends" : [ "base", ], + 'description': """ + Implements IETF RFC 5785 for services discovery on a http server. + + May help some CalDAV clients bootstrap from the OpenERP server. + + Note that it needs explicit configuration in openerp-server.conf . +""", + "author" : "OpenERP SA", + 'category': 'Generic Modules/Others', + 'website': 'http://www.openerp.com', + "init_xml" : [ ], + "demo_xml" : [], + "update_xml" : [ ], + "installable" : True, + "active" : False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/http_well_known/doc/well-known.rst b/addons/http_well_known/doc/well-known.rst new file mode 100644 index 00000000000..ce7dbd7c9cf --- /dev/null +++ b/addons/http_well_known/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/http_well_known/redirect.py b/addons/http_well_known/redirect.py new file mode 100644 index 00000000000..85d01e7b05c --- /dev/null +++ b/addons/http_well_known/redirect.py @@ -0,0 +1,69 @@ +# -*- 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 +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 + + if self.path not in self.redirect_paths: + self.send_error(404, "File not found") + return None + + self.send_response(301) + self.send_header("Location", self.redirect_paths[self.path]) + 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, self.redirect_paths[self.path]) + return None + + def do_PROPFIND(self): + return self.do_HEAD() + + +#eof + From 7db1504d7c6af6404c8d04195ee6dc2c00f8418d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:52:51 +0200 Subject: [PATCH 15/21] http_well_known: have absolute URI at Location header RFC-2616 suggests that the Location URI must be absolute. However, as a server, we can not know if the client called us as "http://" or "webdav://" or else.. Pending some more change to get the ssl scheme from the calling classes. bzr revid: p_christ@hol.gr-20101123185251-qxjgsq2c8bl12puy --- addons/http_well_known/redirect.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/addons/http_well_known/redirect.py b/addons/http_well_known/redirect.py index 85d01e7b05c..153d94bbe45 100644 --- a/addons/http_well_known/redirect.py +++ b/addons/http_well_known/redirect.py @@ -21,6 +21,7 @@ import logging +import urlparse from service.websrv_lib import FixSendError, HTTPHandler, HttpOptions from service.http_server import HttpLogHandler @@ -53,8 +54,25 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler self.send_error(404, "File not found") return None + 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("http://%s:%d"% (addr,port))) + uparts[1] = self.headers['Host'] + baseuri = urlparse.urlunparse(uparts) + else: + baseuri = "http://%s:%d"% (addr, port ) + + + location = baseuri + self.redirect_paths[self.path] + # relative uri: location = self.redirect_paths[self.path] + self.send_response(301) - self.send_header("Location", self.redirect_paths[self.path]) + self.send_header("Location", location) self.send_header("Content-Length", 0) self.end_headers() # Do we need a Cache-content: header here? From 6b7a0b7e42ec0d11e04c2d25e6e68e43b96e28d0 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:52:59 +0200 Subject: [PATCH 16/21] http_well_known: try to retrieve the proto from the http server We must know if we are http or https and redirect accordingly. bzr revid: p_christ@hol.gr-20101123185259-k054y92ruf66n52f --- addons/http_well_known/redirect.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/http_well_known/redirect.py b/addons/http_well_known/redirect.py index 153d94bbe45..9717bae3239 100644 --- a/addons/http_well_known/redirect.py +++ b/addons/http_well_known/redirect.py @@ -54,6 +54,7 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler self.send_error(404, "File not found") 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() @@ -61,11 +62,11 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler self.log_error("Cannot calculate own address:" , e) if self.headers.has_key('Host'): - uparts = list(urlparse.urlparse("http://%s:%d"% (addr,port))) + uparts = list(urlparse.urlparse("%s://%s:%d"% (server_proto, addr,port))) uparts[1] = self.headers['Host'] baseuri = urlparse.urlunparse(uparts) else: - baseuri = "http://%s:%d"% (addr, port ) + baseuri = "%s://%s:%d"% (server_proto, addr, port ) location = baseuri + self.redirect_paths[self.path] From 0b77e833d5d6dc6788a6781778ac7d852091e078 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:53:11 +0200 Subject: [PATCH 17/21] http-well-known: Let it expand, fix body of PROPFIND request Since this class will be used as a class name (not an object instance), we need a per-class way to redefine the redirects. Need also to redirect whole branches rather than path nodes (eg. /principals/... to /webdav/db/principals/...) . Finally, solved the problem that do_PROPFIND would not consume the body of the requests, thus mis-aligning the http layer and breaking the next request. bzr revid: p_christ@hol.gr-20101123185311-o0vlo2je2pkwkg19 --- addons/http_well_known/redirect.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/addons/http_well_known/redirect.py b/addons/http_well_known/redirect.py index 9717bae3239..71af3271ab7 100644 --- a/addons/http_well_known/redirect.py +++ b/addons/http_well_known/redirect.py @@ -50,9 +50,12 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler self.end_headers() return None - if self.path not in self.redirect_paths: + 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 @@ -69,7 +72,7 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler baseuri = "%s://%s:%d"% (server_proto, addr, port ) - location = baseuri + self.redirect_paths[self.path] + location = baseuri + redir_path # relative uri: location = self.redirect_paths[self.path] self.send_response(301) @@ -77,12 +80,29 @@ class RedirectHTTPHandler(HttpLogHandler, FixSendError, HttpOptions, HTTPHandler 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, self.redirect_paths[self.path]) + 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 From 8f2e8ff36fc3eb03b8cd28d5fc43be2dd69d6e76 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:53:38 +0200 Subject: [PATCH 18/21] http-well-known: merge into the document_webdav module Since both "well-known" urls are about webdav[1], we can safely assume that the webdav module is needed when well-known uris are used. The code is much similar, too. http://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml bzr revid: p_christ@hol.gr-20101123185338-az85yl7pbc9gf76z --- addons/document_webdav/__openerp__.py | 5 +- .../doc/well-known.rst | 0 .../redirect.py | 0 addons/document_webdav/webdav_server.py | 25 +++++++++- addons/http_well_known/__init__.py | 46 ------------------- addons/http_well_known/__openerp__.py | 44 ------------------ 6 files changed, 27 insertions(+), 93 deletions(-) rename addons/{http_well_known => document_webdav}/doc/well-known.rst (100%) rename addons/{http_well_known => document_webdav}/redirect.py (100%) delete mode 100644 addons/http_well_known/__init__.py delete mode 100644 addons/http_well_known/__openerp__.py 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/http_well_known/doc/well-known.rst b/addons/document_webdav/doc/well-known.rst similarity index 100% rename from addons/http_well_known/doc/well-known.rst rename to addons/document_webdav/doc/well-known.rst diff --git a/addons/http_well_known/redirect.py b/addons/document_webdav/redirect.py similarity index 100% rename from addons/http_well_known/redirect.py rename to addons/document_webdav/redirect.py diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 5810978a6d5..f2a230858bf 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -1,5 +1,5 @@ # -*- encoding: utf-8 -*- - +############################################################################9 # # Copyright P. Christeas 2008,2009 # Copyright OpenERP SA, 2010 (http://www.openerp.com ) @@ -14,7 +14,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, @@ -43,6 +43,7 @@ import addons 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 redirect import RedirectHTTPHandler khtml_re = re.compile(r' KHTML/([0-9\.]+) ') @@ -453,6 +454,26 @@ except Exception, e: logger = netsvc.Logger() logger.notifyChannel('webdav', netsvc.LOG_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() + #eof diff --git a/addons/http_well_known/__init__.py b/addons/http_well_known/__init__.py deleted file mode 100644 index 45c4efee2e1..00000000000 --- a/addons/http_well_known/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -# -*- 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 tools -from service.websrv_lib import HTTPDir -from service.http_server import reg_http_service -from redirect import RedirectHTTPHandler - -def init_well_known(): - reps = RedirectHTTPHandler.redirect_paths - - num_svcs = tools.config.get_misc('http-well-known', 'num_services', '0') - - for nsv in range(1, int(num_svcs)+1): - uri = tools.config.get_misc('http-well-known', 'service_%d' % nsv, False) - path = tools.config.get_misc('http-well-known', 'path_%d' % nsv, False) - if not (uri and path): - continue - reps['/'+uri] = path - - if reg_http_service(HTTPDir('/.well-known', RedirectHTTPHandler)): - logging.getLogger("web-services").info("Registered HTTP redirect handler at /.well-known" ) - - -init_well_known() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/http_well_known/__openerp__.py b/addons/http_well_known/__openerp__.py deleted file mode 100644 index 090b9a5ee01..00000000000 --- a/addons/http_well_known/__openerp__.py +++ /dev/null @@ -1,44 +0,0 @@ -# -*- 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 . -# -############################################################################## - - -{ - "name" : "Publish well-known http URIs", - "version" : "0.1", - "depends" : [ "base", ], - 'description': """ - Implements IETF RFC 5785 for services discovery on a http server. - - May help some CalDAV clients bootstrap from the OpenERP server. - - Note that it needs explicit configuration in openerp-server.conf . -""", - "author" : "OpenERP SA", - 'category': 'Generic Modules/Others', - 'website': 'http://www.openerp.com', - "init_xml" : [ ], - "demo_xml" : [], - "update_xml" : [ ], - "installable" : True, - "active" : False, -} - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: From 2bdc61232ecfb0fc7b55d48fd1eaaea8184f2d3a Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:53:43 +0200 Subject: [PATCH 19/21] doc webdav: principals redirect feature, for i-things iPhones etc. are preset to look into /principals/users/xx , so let's provide that path to them. bzr revid: p_christ@hol.gr-20101123185343-n1k1szxojdmcpekz --- addons/document_webdav/webdav_server.py | 31 ++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index f2a230858bf..3dae8f9f21f 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -471,9 +471,38 @@ def init_well_known(): 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 From f95f47b4a9a60d464218d64393305ff4edb19014 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 20:53:54 +0200 Subject: [PATCH 20/21] doc webdav: refactor the mk_prop_response() for the locks This prepares the code to be reused in the DAV lock functions. bzr revid: p_christ@hol.gr-20101123185354-a2rjviaep3emqeez --- addons/document_webdav/webdav.py | 179 +++++++++++++++++-------------- 1 file changed, 99 insertions(+), 80 deletions(-) diff --git a/addons/document_webdav/webdav.py b/addons/document_webdav/webdav.py index bb2b8caf470..486595bc241 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) From 369d1d110b3e5715a3cc74e602539410785d6b7f Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 23 Nov 2010 21:01:25 +0200 Subject: [PATCH 21/21] Doc_webdav: port logging to pythonic, not netsvc Having an allocated logging object may even speed up things. bzr revid: p_christ@hol.gr-20101123190125-0l951ev1h9dsu6lw --- addons/document_webdav/webdav_server.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/addons/document_webdav/webdav_server.py b/addons/document_webdav/webdav_server.py index 3dae8f9f21f..71402183893 100644 --- a/addons/document_webdav/webdav_server.py +++ b/addons/document_webdav/webdav_server.py @@ -28,6 +28,7 @@ ############################################################################### +import logging from dav_fs import openerp_dav_handler from tools.config import config from DAV.WebDAVServer import DAVRequestHandler @@ -63,6 +64,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', @@ -72,8 +74,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() @@ -114,10 +117,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 @@ -427,7 +430,7 @@ 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. @@ -451,8 +454,7 @@ try: (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():