From ad31f812bc437b24f3ad0d25795b8b6de980d8ea Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 29 Jun 2010 17:03:11 +0300 Subject: Document_ftp: Don't use the open() method, but the get_data() bzr revid: p_christ@hol.gr-20100629140311-7bvmbrc2t7gjyn1g --- addons/document_ftp/ftpserver/abstracted_fs.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 60b3c24c7a9..7d4eccb05fd 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -311,15 +311,11 @@ class abstracted_fs: raise OSError(1, 'Operation not permited.') # Reading operation cr = pooler.get_db(node.context.dbname).cursor() - res = False - #try: - if node.type not in ('collection','database'): - res = node.open(cr, mode) - #except: - # pass - cr.close() - if not res: - raise OSError(1, 'Operation not permited.') + try: + res = StringIO.StringIO(node.get_data(cr)) + res.name = node + finally: + cr.close() return res # ok, but need test more @@ -423,10 +419,14 @@ class abstracted_fs: return None try: db,pool = pooler.get_db_and_pool(dbname) - except: + except Exception: raise OSError(1, 'Operation not permited.') cr = db.cursor() - uid = security.login(dbname, self.username, self.password) + try: + uid = security.login(dbname, self.username, self.password) + except Exception: + cr.close() + raise if not uid: raise OSError(2, 'Authentification Required.') return cr, uid, pool -- cgit v1.2.3 From d835a1419b2761b9cb178b47c86cc4ee287bc7c9 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 29 Jun 2010 17:03:28 +0300 Subject: Document FTP: don't touch the databases, on db_list Trying a login on each db listed would result in the database loaded by the pooler. This behaviour is not really welcome. However, some clients (like kio_ftp) may still want to descend into the databases listed, in which case we cannot avoid to load the db(s). bzr revid: p_christ@hol.gr-20100629140328-2kqlfpo3oyhu0ufi --- addons/document_ftp/ftpserver/abstracted_fs.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 7d4eccb05fd..a44008dfcfa 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -428,6 +428,7 @@ class abstracted_fs: cr.close() raise if not uid: + cr.close() raise OSError(2, 'Authentification Required.') return cr, uid, pool @@ -445,10 +446,10 @@ class abstracted_fs: result = [] for db in self.db_list(): try: - uid = security.login(db, self.username, self.password) - if uid: - result.append(false_node(db)) - except osv.except_osv: + result.append(false_node(db)) + except osv.except_osv: + import traceback + traceback.print_exc() pass return result cr = pooler.get_db(path.context.dbname).cursor() -- cgit v1.2.3 From d130db349e6caedc789f71064b2dde3888ece7f9 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 29 Jun 2010 17:05:15 +0300 Subject: Document FTP: use pythonic logging in abstract_fs bzr revid: p_christ@hol.gr-20100629140515-u73gs9ks4tiylhuo --- addons/document_ftp/ftpserver/abstracted_fs.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index a44008dfcfa..b9e20214045 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -4,6 +4,7 @@ import time from tarfile import filemode import StringIO import base64 +import logging import glob import fnmatch @@ -16,11 +17,6 @@ from osv import osv from document.nodes import node_res_dir, node_res_obj import stat -def log(message): - logger = netsvc.Logger() - logger.notifyChannel('DMS', netsvc.LOG_ERROR, message) - - def _get_month_name(month): month=int(month) if month==1:return 'Jan' @@ -104,7 +100,7 @@ class content_wrapper(StringIO.StringIO): StringIO.StringIO.close(self, *args, **kwargs) -class abstracted_fs: +class abstracted_fs(object): """A class used to interact with the file system, providing a high level, cross-platform interface compatible with both Windows and UNIX style filesystems. @@ -140,8 +136,8 @@ class abstracted_fs: if res and len(res): self.db_name_list.append(db_name) cr.commit() - except Exception, e: - log(e) + except Exception: + self._log.warning('Cannot use db "%s"', db_name) finally: if cr is not None: cr.close() @@ -154,6 +150,7 @@ class abstracted_fs: self.root = None self.cwd = '/' self.rnfr = None + self._log = logging.getLogger('FTP.fs') # --- Pathname / conversion utilities @@ -299,7 +296,7 @@ class abstracted_fs: s = file_wrapper('', cid, node.context.dbname, uid, ) return s except Exception,e: - log(e) + self._log.exception('Cannot create item %s at node %s', objname, repr(node)) raise OSError(1, 'Operation not permited.') finally: if cr: @@ -399,7 +396,7 @@ class abstracted_fs: pool.get('document.directory').create(cr, uid, val) cr.commit() except Exception,e: - log(e) + self._log.exception('Cannot create dir "%s" at node %s', basename, repr(node)) raise OSError(1, 'Operation not permited.') finally: if cr: cr.close() @@ -448,8 +445,6 @@ class abstracted_fs: try: result.append(false_node(db)) except osv.except_osv: - import traceback - traceback.print_exc() pass return result cr = pooler.get_db(path.context.dbname).cursor() @@ -513,6 +508,7 @@ class abstracted_fs: """ cr = False try: + # FIXME! wrong code here, doesn't use the node API dst_basename = _to_unicode(dst_basename) cr = pooler.get_db(src.context.dbname).cursor() uid = src.context.uid @@ -632,7 +628,7 @@ class abstracted_fs: else: raise OSError(1, 'Operation not permited.') except Exception,err: - log(err) + self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir) raise OSError(1,'Operation not permited.') finally: if cr: cr.close() -- cgit v1.2.3 From b2a10c1b20a8e7b416d56a9bb1ac9cfef47e3a97 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 29 Jun 2010 17:05:32 +0300 Subject: Document FTP: adapt to node_descriptors API bzr revid: p_christ@hol.gr-20100629140532-xgpjex7h0q1g0cff --- addons/document_ftp/ftpserver/abstracted_fs.py | 126 ++++++++----------------- addons/document_ftp/ftpserver/ftpserver.py | 2 +- 2 files changed, 39 insertions(+), 89 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index b9e20214045..2b61ffd86e9 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -14,7 +14,8 @@ import netsvc import os from service import security from osv import osv -from document.nodes import node_res_dir, node_res_obj +#from document.nodes import node_res_dir, node_res_obj +from document.nodes import get_node_context import stat def _get_month_name(month): @@ -55,50 +56,6 @@ def _to_decode(s): return s.decode('ascii') except UnicodeError: return s - - -class file_wrapper(StringIO.StringIO): - def __init__(self, sstr='', ressource_id=False, dbname=None, uid=1, name=''): - StringIO.StringIO.__init__(self, sstr) - self.ressource_id = ressource_id - self.name = name - self.dbname = dbname - self.uid = uid - def close(self, *args, **kwargs): - db,pool = pooler.get_db_and_pool(self.dbname) - cr = db.cursor() - cr.commit() - try: - val = self.getvalue() - val2 = { - 'datas': base64.encodestring(val), - 'file_size': len(val), - } - pool.get('ir.attachment').write(cr, self.uid, [self.ressource_id], val2) - finally: - cr.commit() - cr.close() - StringIO.StringIO.close(self, *args, **kwargs) - -class content_wrapper(StringIO.StringIO): - def __init__(self, dbname, uid, pool, node, name=''): - StringIO.StringIO.__init__(self, '') - self.dbname = dbname - self.uid = uid - self.node = node - self.pool = pool - self.name = name - def close(self, *args, **kwargs): - db,pool = pooler.get_db_and_pool(self.dbname) - cr = db.cursor() - cr.commit() - try: - getattr(self.pool.get('document.directory.content'), 'process_write')(cr, self.uid, self.node, self.getvalue()) - finally: - cr.commit() - cr.close() - StringIO.StringIO.close(self, *args, **kwargs) - class abstracted_fs(object): """A class used to interact with the file system, providing a high @@ -186,14 +143,16 @@ class abstracted_fs(object): # Ok def ftp2fs(self, path_orig, data): - path = self.ftpnorm(path_orig) + path = self.ftpnorm(path_orig) if not data or (path and path=='/'): return None path2 = filter(None,path.split('/'))[1:] - (cr, uid, pool) = data + (cr, uid) = data if len(path2): - path2[-1]=_to_unicode(path2[-1]) - res = pool.get('document.directory').get_object(cr, uid, path2[:]) + path2[-1]=_to_unicode(path2[-1]) + + ctx = get_node_context(cr, uid, {}) + res = ctx.get_uri(cr, path2[:]) if not res: raise OSError(2, 'Not such file or directory.') return res @@ -302,15 +261,13 @@ class abstracted_fs(object): if cr: cr.close() - # Ok def open(self, node, mode): if not node: raise OSError(1, 'Operation not permited.') # Reading operation cr = pooler.get_db(node.context.dbname).cursor() try: - res = StringIO.StringIO(node.get_data(cr)) - res.name = node + res = node.open_data(cr, mode) finally: cr.close() return res @@ -401,7 +358,6 @@ class abstracted_fs(object): finally: if cr: cr.close() - # Ok def close_cr(self, data): if data: data[0].close() @@ -415,7 +371,7 @@ class abstracted_fs(object): if dbname not in self.db_list(): return None try: - db,pool = pooler.get_db_and_pool(dbname) + db = pooler.get_db(dbname) except Exception: raise OSError(1, 'Operation not permited.') cr = db.cursor() @@ -427,9 +383,22 @@ class abstracted_fs(object): if not uid: cr.close() raise OSError(2, 'Authentification Required.') - return cr, uid, pool + return cr, uid - # Ok + def get_node_cr_uid(self, node): + """ Get cr, uid, pool from a node + """ + db = pooler.get_db(node.context.dbname) + return db.cursor(), node.context.uid + + def get_node_cr(self, node): + """ Get the cursor for the database of a node + + The cursor is the only thing that a node will not store + persistenly, so we have to obtain a new one for each call. + """ + return self.get_node_cr_uid(node)[0] + def listdir(self, path): """List the content of a directory.""" class false_node(object): @@ -447,32 +416,21 @@ class abstracted_fs(object): except osv.except_osv: pass return result - cr = pooler.get_db(path.context.dbname).cursor() + cr = self.get_node_cr(path) res = path.children(cr) cr.close() return res - # Ok def rmdir(self, node): """Remove the specified directory.""" assert node - cr = pooler.get_db(node.context.dbname).cursor() - uid = node.context.uid - pool = pooler.get_pool(node.context.dbname) - object = node.context._dirobj.browse(cr, uid, node.dir_id) - if not object: - raise OSError(2, 'Not such file or directory.') - if object._table_name == 'document.directory': - if node.children(cr): - raise OSError(39, 'Directory not empty.') - res = pool.get('document.directory').unlink(cr, uid, [object.id]) - else: - raise OSError(1, 'Operation not permited.') - - cr.commit() - cr.close() + cr = self.get_node_cr(node) + try: + node.rmcol(cr) + cr.commit() + finally: + cr.close() - # Ok def remove(self, node): assert node if node.type == 'collection': @@ -484,20 +442,12 @@ class abstracted_fs(object): def rmfile(self, node): """Remove the specified file.""" assert node - if node.type == 'collection': - return self.rmdir(node) - uid = node.context.uid - pool = pooler.get_pool(node.context.dbname) - cr = pooler.get_db(node.context.dbname).cursor() - object = pool.get('ir.attachment').browse(cr, uid, node.file_id) - if not object: - raise OSError(2, 'Not such file or directory.') - if object._table_name == 'ir.attachment': - res = pool.get('ir.attachment').unlink(cr, uid, [object.id]) - else: - raise OSError(1, 'Operation not permited.') - cr.commit() - cr.close() + cr = self.get_node_cr(node) + try: + node.rm(cr) + cr.commit() + finally: + cr.close() # Ok def rename(self, src, dst_basedir, dst_basename): diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index 3a85b3061a7..c02323dbc53 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -793,7 +793,7 @@ class DTPHandler(asyncore.dispatcher): if self.transfer_finished: self.cmd_channel.respond("226 Transfer complete.") if self.file_obj: - fname = self.cmd_channel.fs.fs2ftp(self.file_obj.name) + fname = self.file_obj.name self.cmd_channel.log('"%s" %s.' %(fname, action)) else: tot_bytes = self.get_transmitted_bytes() -- cgit v1.2.3 From abebb5c31194e86af9bf6326f4f6a4980f17be1c Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 1 Jul 2010 20:51:32 +0300 Subject: Document_ftp: *only* serve databases that have the module installed. The document_ftp is not really a database-specific server, but a server-wide implementation. So, it could normally serve all the databases of the server, once one of them has it installed. Limit that and instead only serve the dbs that have the module marked as installed. bzr revid: p_christ@hol.gr-20100701175132-dydehiw5m6nnvcwy --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 2b61ffd86e9..48c57cfc57b 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -88,7 +88,7 @@ class abstracted_fs(object): if not cr.fetchone(): continue - cr.execute("select id from ir_module_module where name like 'document%' and state='installed' ") + cr.execute("SELECT id FROM ir_module_module WHERE name = 'document_ftp' AND state='installed' ") res = cr.fetchone() if res and len(res): self.db_name_list.append(db_name) -- cgit v1.2.3 From fb3f520268ce6437cdee1529b5bd974426af1e42 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Thu, 1 Jul 2010 20:51:34 +0300 Subject: Document ftp: use create, coll_create, rename from node API bzr revid: p_christ@hol.gr-20100701175134-g5wkaweznslhajgm --- addons/document_ftp/ftpserver/abstracted_fs.py | 249 +++---------------------- 1 file changed, 31 insertions(+), 218 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 48c57cfc57b..cb711c9d7fd 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -187,73 +187,31 @@ class abstracted_fs(object): # --- Wrapper methods around open() and tempfile.mkstemp - # Ok def create(self, node, objname, mode): + """ Create a children file-node under node, open it + @return open node_descriptor of the created node + """ objname = _to_unicode(objname) - cr = None + cr = self.get_node_cr(node) try: - uid = node.context.uid - pool = pooler.get_pool(node.context.dbname) - cr = pooler.get_db(node.context.dbname).cursor() child = node.child(cr, objname) if child: - if child.type in ('collection','database'): + if child.type not in ('file','content'): raise OSError(1, 'Operation not permited.') - if child.type == 'content': - s = content_wrapper(node.context.dbname, uid, pool, child) - return s - fobj = pool.get('ir.attachment') - ext = objname.find('.') >0 and objname.split('.')[1] or False - - # TODO: test if already exist and modify in this case if node.type=file - ### checked already exits - object2 = False - if isinstance(node, node_res_obj): - object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False - - cid = False - object = node.context._dirobj.browse(cr, uid, node.dir_id) - where = [('name','=',objname)] - if object and (object.type in ('directory')) or object2: - where.append(('parent_id','=',object.id)) - else: - where.append(('parent_id','=',False)) - - if object2: - where += [('res_id','=',object2.id),('res_model','=',object2._name)] - cids = fobj.search(cr, uid, where) - if len(cids): - cid = cids[0] - - if not cid: - val = { - 'name': objname, - 'datas_fname': objname, - 'parent_id' : node.dir_id, - 'datas': '', - 'file_size': 0L, - 'file_type': ext, - 'store_method' : (object.storage_id.type == 'filestore' and 'fs')\ - or (object.storage_id.type == 'db' and 'db') - } - if object and (object.type in ('directory')) or not object2: - val['parent_id']= object and object.id or False - partner = False - if object2: - if 'partner_id' in object2 and object2.partner_id.id: - partner = object2.partner_id.id - if object2._name == 'res.partner': - partner = object2.id - val.update( { - 'res_model': object2._name, - 'partner_id': partner, - 'res_id': object2.id - }) - cid = fobj.create(cr, uid, val, context={}) - cr.commit() - s = file_wrapper('', cid, node.context.dbname, uid, ) - return s + ret = child.open_data(cr, mode) + cr.close() + return ret + except OSError: + cr.close() + raise + except Exception,e: + self._log.exception('Cannot locate item %s at node %s', objname, repr(node)) + pass + + try: + child = node.create_child(cr, objname, data=None) + return child.open_data(cr, mode) except Exception,e: self._log.exception('Cannot create item %s at node %s', objname, repr(node)) raise OSError(1, 'Operation not permited.') @@ -265,7 +223,7 @@ class abstracted_fs(object): if not node: raise OSError(1, 'Operation not permited.') # Reading operation - cr = pooler.get_db(node.context.dbname).cursor() + cr = self.get_node_cr(node) try: res = node.open_data(cr, mode) finally: @@ -279,24 +237,12 @@ class abstracted_fs(object): name. Unlike mkstemp it returns an object with a file-like interface. """ - raise 'Not Yet Implemented' -# class FileWrapper: -# def __init__(self, fd, name): -# self.file = fd -# self.name = name -# def __getattr__(self, attr): -# return getattr(self.file, attr) -# -# text = not 'b' in mode -# # max number of tries to find out a unique file name -# tempfile.TMP_MAX = 50 -# fd, name = tempfile.mkstemp(suffix, prefix, dir, text=text) -# file = os.fdopen(fd, mode) -# return FileWrapper(file, name) + raise NotImplementedError text = not 'b' in mode # for unique file , maintain version if duplicate file if dir: + # TODO cr = dir.cr uid = dir.uid pool = pooler.get_pool(node.context.dbname) @@ -330,28 +276,12 @@ class abstracted_fs(object): cr = False if not node: raise OSError(1, 'Operation not permited.') + + cr, uid = self.get_node_cr(node) try: basename =_to_unicode(basename) - cr = pooler.get_db(node.context.dbname).cursor() - uid = node.context.uid - pool = pooler.get_pool(node.context.dbname) - object2 = False - if isinstance(node, node_res_obj): - object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False - obj = node.context._dirobj.browse(cr, uid, node.dir_id) - if obj and (obj.type == 'ressource') and not object2: - raise OSError(1, 'Operation not permited.') - val = { - 'name': basename, - 'ressource_parent_type_id': obj and obj.ressource_type_id.id or False, - 'ressource_id': object2 and object2.id or False, - 'parent_id' : False - } - if (obj and (obj.type in ('directory'))) or not object2: - val['parent_id'] = obj and obj.id or False - # Check if it alreayd exists ! - pool.get('document.directory').create(cr, uid, val) - cr.commit() + cdir = node.create_child_collection(cr, basename) + self._log.debug("Created child dir: %r", cdir) except Exception,e: self._log.exception('Cannot create dir "%s" at node %s', basename, repr(node)) raise OSError(1, 'Operation not permited.') @@ -449,134 +379,17 @@ class abstracted_fs(object): finally: cr.close() - # Ok def rename(self, src, dst_basedir, dst_basename): - """ - Renaming operation, the effect depends on the src: + """ Renaming operation, the effect depends on the src: * A file: read, create and remove * A directory: change the parent and reassign childs to ressource """ - cr = False + cr = self.get_node_cr(src) try: - # FIXME! wrong code here, doesn't use the node API - dst_basename = _to_unicode(dst_basename) - cr = pooler.get_db(src.context.dbname).cursor() - uid = src.context.uid - if src.type == 'collection': - obj2 = False - dst_obj2 = False - pool = pooler.get_pool(src.context.dbname) - if isinstance(src, node_res_obj): - obj2 = src and pool.get(src.context.context['res_model']).browse(cr, uid, src.context.context['res_id']) or False - obj = src.context._dirobj.browse(cr, uid, src.dir_id) - if isinstance(dst_basedir, node_res_obj): - dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False - dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id) - if obj._table_name <> 'document.directory': - raise OSError(1, 'Operation not permited.') - result = { - 'directory': [], - 'attachment': [] - } - # Compute all childs to set the new ressource ID - child_ids = [src] - while len(child_ids): - node = child_ids.pop(0) - child_ids += node.children(cr) - if node.type == 'collection': - object2 = False - if isinstance(node, node_res_obj): - object2 = node and pool.get(node.context.context['res_model']).browse(cr, uid, node.context.context['res_id']) or False - object = node.context._dirobj.browse(cr, uid, node.dir_id) - result['directory'].append(object.id) - if (not object.ressource_id) and object2: - raise OSError(1, 'Operation not permited.') - elif node.type == 'file': - result['attachment'].append(object.id) - - if obj2 and not obj.ressource_id: - raise OSError(1, 'Operation not permited.') - - if (dst_obj and (dst_obj.type in ('directory'))) or not dst_obj2: - parent_id = dst_obj and dst_obj.id or False - else: - parent_id = False - - - if dst_obj2: - ressource_type_id = pool.get('ir.model').search(cr, uid, [('model','=',dst_obj2._name)])[0] - ressource_id = dst_obj2.id - ressource_model = dst_obj2._name - if dst_obj2._name == 'res.partner': - partner_id = dst_obj2.id - else: - partner_id = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False - else: - ressource_type_id = False - ressource_id = False - ressource_model = False - partner_id = False - pool.get('document.directory').write(cr, uid, result['directory'], { - 'name' : dst_basename, - 'ressource_id': ressource_id, - 'ressource_parent_type_id': ressource_type_id, - 'parent_id' : parent_id - }) - val = { - 'res_id': ressource_id, - 'res_model': ressource_model, - 'partner_id': partner_id - } - pool.get('ir.attachment').write(cr, uid, result['attachment'], val) - if (not val['res_id']) and result['attachment']: - cr.execute('update ir_attachment set res_id=NULL where id in ('+','.join(map(str,result['attachment']))+')') - - cr.commit() - - elif src.type == 'file': - pool = pooler.get_pool(src.context.dbname) - obj = pool.get('ir.attachment').browse(cr, uid, src.file_id) - dst_obj2 = False - if isinstance(dst_basedir, node_res_obj): - dst_obj2 = dst_basedir and pool.get(dst_basedir.context.context['res_model']).browse(cr, uid, dst_basedir.context.context['res_id']) or False - dst_obj = dst_basedir.context._dirobj.browse(cr, uid, dst_basedir.dir_id) - - val = { - 'partner_id':False, - #'res_id': False, - 'res_model': False, - 'name': dst_basename, - 'datas_fname': dst_basename, - } - - if (dst_obj and (dst_obj.type in ('directory','ressource'))) or not dst_obj2: - val['parent_id'] = dst_obj and dst_obj.id or False - else: - val['parent_id'] = False - - if dst_obj2: - val['res_model'] = dst_obj2._name - val['res_id'] = dst_obj2.id - if dst_obj2._name == 'res.partner': - val['partner_id'] = dst_obj2.id - else: - val['partner_id'] = pool.get(dst_obj2._name).fields_get(cr, uid, ['partner_id']) and dst_obj2.partner_id.id or False - elif obj.res_id: - # I had to do that because writing False to an integer writes 0 instead of NULL - # change if one day we decide to improve osv/fields.py - cr.execute('update ir_attachment set res_id=NULL where id=%s', (obj.id,)) - - pool.get('ir.attachment').write(cr, uid, [obj.id], val) - cr.commit() - elif src.type=='content': - src_file = self.open(src,'r') - dst_file = self.create(dst_basedir, dst_basename, 'w') - dst_file.write(src_file.getvalue()) - dst_file.close() - src_file.close() - cr.commit() - else: - raise OSError(1, 'Operation not permited.') + nname = _to_unicode(dst_basename) + ret = src.move_to(cr, dst_basedir, new_name=nname) + # API shouldn't wait for us to write the object + assert (ret is True) or (ret is False) except Exception,err: self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir) raise OSError(1,'Operation not permited.') -- cgit v1.2.3 From 5da9c2660533a6ecfabf3359d900da3b4bdb24b3 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 2 Jul 2010 01:43:08 +0300 Subject: Document FTP: fix syntax error in mkdir. bzr revid: p_christ@hol.gr-20100701224308-g95nx5ckh8mgjgsb --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index cb711c9d7fd..710d49f7dc4 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -277,7 +277,7 @@ class abstracted_fs(object): if not node: raise OSError(1, 'Operation not permited.') - cr, uid = self.get_node_cr(node) + cr = self.get_node_cr(node) try: basename =_to_unicode(basename) cdir = node.create_child_collection(cr, basename) -- cgit v1.2.3 From 88b0420b3e06a06029755a9593b41be260ec4a0e Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 2 Jul 2010 01:43:11 +0300 Subject: Document ftp: do commit the cursors, at mkdir and rename cr.close() is not enough for data to go to db. bzr revid: p_christ@hol.gr-20100701224311-ef8q4qbmb8b9j6p1 --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 710d49f7dc4..5bb0f674fa2 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -282,6 +282,7 @@ class abstracted_fs(object): basename =_to_unicode(basename) cdir = node.create_child_collection(cr, basename) self._log.debug("Created child dir: %r", cdir) + cr.commit() except Exception,e: self._log.exception('Cannot create dir "%s" at node %s', basename, repr(node)) raise OSError(1, 'Operation not permited.') @@ -390,6 +391,7 @@ class abstracted_fs(object): ret = src.move_to(cr, dst_basedir, new_name=nname) # API shouldn't wait for us to write the object assert (ret is True) or (ret is False) + cr.commit() except Exception,err: self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir) raise OSError(1,'Operation not permited.') -- cgit v1.2.3 From dce6c870841df6ff43397bed17aea43e2da06350 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 2 Jul 2010 13:38:58 +0300 Subject: Document ftp: stop early if not in a node. This will rather help debug some cases, where we try operations on the "/" path. bzr revid: p_christ@hol.gr-20100702103858-8jrqfzkd8g65vrzr --- addons/document_ftp/ftpserver/abstracted_fs.py | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 5bb0f674fa2..0766d42d501 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -319,6 +319,7 @@ class abstracted_fs(object): def get_node_cr_uid(self, node): """ Get cr, uid, pool from a node """ + assert node db = pooler.get_db(node.context.dbname) return db.cursor(), node.context.uid -- cgit v1.2.3 From e29f5bef650ec823ab2f4c6d67cf494949a73d20 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sat, 3 Jul 2010 00:52:49 +0300 Subject: Document FTP: rewrite the code, resolve nodes only once. A big patch: nodes should not be calculated multiple times from the path string, within an operation. Instead, they are resolved only once in a node object (may reuse the cwd_node, if possible) and then op relative to that node. bzr revid: p_christ@hol.gr-20100702215249-kzucr3i1p5f6vvnt --- addons/document_ftp/ftpserver/abstracted_fs.py | 442 +++++++++++++------------ addons/document_ftp/ftpserver/ftpserver.py | 275 +++++++-------- 2 files changed, 339 insertions(+), 378 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 0766d42d501..60974896b41 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -5,6 +5,7 @@ from tarfile import filemode import StringIO import base64 import logging +import errno import glob import fnmatch @@ -70,11 +71,20 @@ class abstracted_fs(object): - (str) root: the user home directory. - (str) cwd: the current working directory. - (str) rnfr: source file to be renamed. + """ + def __init__(self): + self.root = None + self.cwd = '/' + self.cwd_node = None + self.rnfr = None + self._log = logging.getLogger('FTP.fs') + # Ok def db_list(self): - #return pooler.pool_dic.keys() + """Get the list of available databases, with FTPd support + """ s = netsvc.ExportService.getService('db') result = s.exp_list(document=True) self.db_name_list = [] @@ -102,97 +112,58 @@ class abstracted_fs(object): # pooler.close_db(db_name) return self.db_name_list - # Ok - def __init__(self): - self.root = None - self.cwd = '/' - self.rnfr = None - self._log = logging.getLogger('FTP.fs') - - # --- Pathname / conversion utilities - - # Ok def ftpnorm(self, ftppath): """Normalize a "virtual" ftp pathname (tipically the raw string - coming from client) depending on the current working directory. + coming from client). - Example (having "/foo" as current working directory): - 'x' -> '/foo/x' - - Note: directory separators are system independent ("/"). - Pathname returned is always absolutized. + Pathname returned is relative!. """ - if os.path.isabs(ftppath): - p = os.path.normpath(ftppath) - else: - p = os.path.normpath(os.path.join(self.cwd, ftppath)) + p = os.path.normpath(ftppath) # normalize string in a standard web-path notation having '/' - # as separator. + # as separator. xrg: is that really in the spec? p = p.replace("\\", "/") # os.path.normpath supports UNC paths (e.g. "//a/b/c") but we # don't need them. In case we get an UNC path we collapse # redundant separators appearing at the beginning of the string while p[:2] == '//': p = p[1:] - # Anti path traversal: don't trust user input, in the event - # that self.cwd is not absolute, return "/" as a safety measure. - # This is for extra protection, maybe not really necessary. - if not os.path.isabs(p): - p = "/" + if p == '.': + return '' return p - # Ok + def get_cwd(self): + """ return the cwd, decoded in utf""" + return _to_decode(self.cwd) + def ftp2fs(self, path_orig, data): - path = self.ftpnorm(path_orig) - if not data or (path and path=='/'): - return None - path2 = filter(None,path.split('/'))[1:] - (cr, uid) = data - if len(path2): - path2[-1]=_to_unicode(path2[-1]) - - ctx = get_node_context(cr, uid, {}) - res = ctx.get_uri(cr, path2[:]) - if not res: - raise OSError(2, 'Not such file or directory.') - return res + raise DeprecationWarning() - # Ok def fs2ftp(self, node): + """ Return the string path of a node, in ftp form + """ res='/' if node: paths = node.full_path() - paths = map(lambda x: '/' +x, paths) - res = os.path.normpath(''.join(paths)) - res = res.replace("\\", "/") - while res[:2] == '//': - res = res[1:] - res = '/' + node.context.dbname + '/' + _to_decode(res) + res = '/' + node.context.dbname + '/' + \ + _to_decode(os.path.join(*paths)) - #res = node and ('/' + node.cr.dbname + '/' + _to_decode(self.ftpnorm(node.path))) or '/' return res - # Ok def validpath(self, path): """Check whether the path belongs to user's home directory. - Expected argument is a "real" filesystem pathname. - - If path is a symbolic link it is resolved to check its real - destination. - - Pathnames escaping from user's root directory are considered - not valid. + Expected argument is a datacr tuple """ - return path and True or False + # TODO: are we called for "/" ? + return isinstance(path, tuple) and path[1] and True or False # --- Wrapper methods around open() and tempfile.mkstemp - def create(self, node, objname, mode): + def create(self, datacr, objname, mode): """ Create a children file-node under node, open it @return open node_descriptor of the created node """ objname = _to_unicode(objname) - cr = self.get_node_cr(node) + cr , node, rem = datacr try: child = node.child(cr, objname) if child: @@ -200,10 +171,8 @@ class abstracted_fs(object): raise OSError(1, 'Operation not permited.') ret = child.open_data(cr, mode) - cr.close() return ret - except OSError: - cr.close() + except EnvironmentError: raise except Exception,e: self._log.exception('Cannot locate item %s at node %s', objname, repr(node)) @@ -215,19 +184,13 @@ class abstracted_fs(object): except Exception,e: self._log.exception('Cannot create item %s at node %s', objname, repr(node)) raise OSError(1, 'Operation not permited.') - finally: - if cr: - cr.close() - def open(self, node, mode): - if not node: + def open(self, datacr, mode): + if not (datacr and datacr[1]): raise OSError(1, 'Operation not permited.') # Reading operation - cr = self.get_node_cr(node) - try: - res = node.open_data(cr, mode) - finally: - cr.close() + cr, node, rem = datacr + res = node.open_data(cr, mode) return res # ok, but need test more @@ -258,26 +221,26 @@ class abstracted_fs(object): # Ok - def chdir(self, path): - if not path: + def chdir(self, datacr): + if (not datacr) or datacr == (None, None, None): self.cwd = '/' + self.cwd_node = None return None - if path.type in ('collection','database'): - self.cwd = self.fs2ftp(path) - elif path.type in ('file'): - parent_path = path.full_path()[:-1] - self.cwd = os.path.normpath(''.join(parent_path)) - else: - raise OSError(1, 'Operation not permited.') + if not datacr[1]: + raise OSError(1, 'Operation not permitted') + if datacr[1].type not in ('collection','database'): + raise OSError(2, 'Path is not a directory') + self.cwd = '/'+datacr[1].context.dbname + '/' + self.cwd += '/'.join(datacr[1].full_path()) + self.cwd_node = datacr[1] # Ok - def mkdir(self, node, basename): + def mkdir(self, datacr, basename): """Create the specified directory.""" - cr = False + cr, node, rem = datacr or (None, None, None) if not node: raise OSError(1, 'Operation not permited.') - cr = self.get_node_cr(node) try: basename =_to_unicode(basename) cdir = node.create_child_collection(cr, basename) @@ -290,31 +253,87 @@ class abstracted_fs(object): if cr: cr.close() def close_cr(self, data): - if data: + if data and data[0]: data[0].close() return True - def get_cr(self, path): - path = self.ftpnorm(path) - if path=='/': - return None - dbname = path.split('/')[1] - if dbname not in self.db_list(): - return None - try: - db = pooler.get_db(dbname) - except Exception: - raise OSError(1, 'Operation not permited.') - cr = db.cursor() - try: - uid = security.login(dbname, self.username, self.password) - except Exception: - cr.close() - raise - if not uid: - cr.close() - raise OSError(2, 'Authentification Required.') - return cr, uid + def get_cr(self, pathname): + raise DeprecationWarning() + + def get_crdata(self, line, mode='file'): + """ Get database cursor, node and remainder data, for commands + + This is the helper function that will prepare the arguments for + any of the subsequent commands. + It returns a tuple in the form of: + @code ( cr, node, rem_path=None ) + + @param line An absolute or relative ftp path, as passed to the cmd. + @param mode A word describing the mode of operation, so that this + function behaves properly in the different commands. + """ + path = self.ftpnorm(line) + if self.cwd_node is None: + if not os.path.isabs(path): + path = os.path.join(self.root, path) + + if path == '/' and mode in ('list', 'cwd'): + return (None, None, None ) + + path = os.path.normpath(path) # again, for '/db/../ss' + if path == '.': path = '' + + if os.path.isabs(path) and self.cwd_node is not None \ + and path.startswith(self.cwd): + # make relative, so that cwd_node is used again + path = path[len(self.cwd):] + if path.startswith('/'): + path = path[1:] + + p_parts = path.split('/') # hard-code the unix sep here, by spec. + + assert '..' not in p_parts + + rem_path = None + if mode in ('create',): + rem_path = p_parts[-1] + p_parts = p_parts[:-1] + + if os.path.isabs(path): + # we have to start from root, again + p_parts = p_parts[1:] + dbname = p_parts[0] + if dbname not in self.db_list(): + return None + try: + db = pooler.get_db(dbname) + except Exception: + raise OSError(1, 'Database cannot be used.') + cr = db.cursor() + try: + uid = security.login(dbname, self.username, self.password) + except Exception: + cr.close() + raise + if not uid: + cr.close() + raise OSError(2, 'Authentification Required.') + n = get_node_context(cr, uid, {}) + node = n.get_uri(cr, p_parts[1:]) + return (cr, node, rem_path) + else: + # we never reach here if cwd_node is not set + if p_parts and p_parts[-1] == '': + p_parts = p_parts[:-1] + cr, uid = self.get_node_cr_uid(self.cwd_node) + if p_parts: + node = self.cwd_node.get_uri(cr, p_parts) + else: + node = self.cwd_node + if node is False and mode not in ('???'): + cr.close() + raise IOError(errno.ENOENT, 'Path does not exist') + return (cr, node, rem_path) def get_node_cr_uid(self, node): """ Get cr, uid, pool from a node @@ -331,16 +350,21 @@ class abstracted_fs(object): """ return self.get_node_cr_uid(node)[0] - def listdir(self, path): + def listdir(self, datacr): """List the content of a directory.""" class false_node(object): - write_date = None - create_date = None + write_date = 0.0 + create_date = 0.0 + unixperms = 040550 + content_length = 0L + uuser = 'root' + ugroup = 'root' type = 'database' + def __init__(self, db): - self.path = '/'+db + self.path = db - if path is None: + if datacr[1] is None: result = [] for db in self.db_list(): try: @@ -348,76 +372,57 @@ class abstracted_fs(object): except osv.except_osv: pass return result - cr = self.get_node_cr(path) - res = path.children(cr) - cr.close() + cr, node, rem = datacr + res = node.children(cr) return res - def rmdir(self, node): + def rmdir(self, datacr): """Remove the specified directory.""" + cr, node, rem = datacr assert node cr = self.get_node_cr(node) - try: - node.rmcol(cr) - cr.commit() - finally: - cr.close() - - def remove(self, node): - assert node - if node.type == 'collection': - return self.rmdir(node) - elif node.type == 'file': - return self.rmfile(node) + node.rmcol(cr) + cr.commit() + + def remove(self, datacr): + assert datacr[1] + if datacr[1].type == 'collection': + return self.rmdir(datacr) + elif datacr[1].type == 'file': + return self.rmfile(datacr) raise OSError(1, 'Operation not permited.') - def rmfile(self, node): + def rmfile(self, datacr): """Remove the specified file.""" - assert node - cr = self.get_node_cr(node) - try: - node.rm(cr) - cr.commit() - finally: - cr.close() + assert datacr[1] + cr = datacr[0] + datacr[1].rm(cr) + cr.commit() - def rename(self, src, dst_basedir, dst_basename): + def rename(self, src, datacr): """ Renaming operation, the effect depends on the src: * A file: read, create and remove * A directory: change the parent and reassign childs to ressource """ - cr = self.get_node_cr(src) + cr = datacr[0] try: - nname = _to_unicode(dst_basename) - ret = src.move_to(cr, dst_basedir, new_name=nname) + nname = _to_unicode(datacr[2]) + ret = src.move_to(cr, datacr[1], new_name=nname) # API shouldn't wait for us to write the object assert (ret is True) or (ret is False) cr.commit() except Exception,err: self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir) raise OSError(1,'Operation not permited.') - finally: - if cr: cr.close() - - - # Nearly Ok def stat(self, node): - r = list(os.stat('/')) - if self.isfile(node): - r[0] = 33188 - r[6] = self.getsize(node) - r[7] = self.getmtime(node) - r[8] = self.getmtime(node) - r[9] = self.getmtime(node) - return os.stat_result(r) - lstat = stat + raise NotImplementedError() # --- Wrapper methods around os.path.* # Ok def isfile(self, node): - if node and (node.type not in ('collection','database')): + if node and (node.type in ('file','content')): return True return False @@ -426,7 +431,6 @@ class abstracted_fs(object): """Return True if path is a symbolic link.""" return False - # Ok def isdir(self, node): """Return True if path is a directory.""" if node is None: @@ -435,19 +439,20 @@ class abstracted_fs(object): return True return False - # Ok - def getsize(self, node): + def getsize(self, datacr): """Return the size of the specified file in bytes.""" - result = 0L - if node.type=='file': - result = node.content_length or 0L - return result + if not (datacr and datacr[1]): + return 0L + if datacr[1].type in ('file', 'content'): + return datacr[1].content_length or 0L + return 0L # Ok - def getmtime(self, node): + def getmtime(self, datacr): """Return the last modified time as a number of seconds since the epoch.""" + node = datacr[1] if node.write_date or node.create_date: dt = (node.write_date or node.create_date)[:19] result = time.mktime(time.strptime(dt, '%Y-%m-%d %H:%M:%S')) @@ -468,7 +473,9 @@ class abstracted_fs(object): """Return True if path refers to an existing path, including a broken or circular symbolic link. """ + raise DeprecationWarning() return path and True or False + exists = lexists # Ok, can be improved @@ -487,23 +494,21 @@ class abstracted_fs(object): # note: the following operations are no more blocking - # Ok - def get_list_dir(self, path): + def get_list_dir(self, datacr): """"Return an iterator object that yields a directory listing in a form suitable for LIST command. """ - if self.isdir(path): - listing = self.listdir(path) + if not datacr: + return None + elif self.isdir(datacr[1]): + listing = self.listdir(datacr) #listing.sort() - return self.format_list(path and path.path or '/', listing) + return self.format_list(datacr[0], datacr[1], listing) # if path is a file or a symlink we return information about it - elif self.isfile(path): - basedir, filename = os.path.split(path.path) - self.lstat(path) # raise exc in case of problems - return self.format_list(basedir, [path]) - + elif self.isfile(datacr[1]): + par = datacr[1].parent + return self.format_list(datacr[0], par, [datacr[1]]) - # Ok def get_stat_dir(self, rawline, datacr): """Return an iterator object that yields a list of files matching a dirname pattern non-recursively in a form @@ -526,13 +531,12 @@ class abstracted_fs(object): listing.sort() return self.format_list(basedir, listing) - # Ok - def format_list(self, basedir, listing, ignore_err=True): + def format_list(self, cr, parent_node, listing, ignore_err=True): """Return an iterator object that yields the entries of given directory emulating the "/bin/ls -lA" UNIX command output. - - (str) basedir: the absolute dirname. - - (list) listing: the names of the entries in basedir + - (str) basedir: the parent directory node. Can be None + - (list) listing: a list of nodes - (bool) ignore_err: when False raise exception if os.lstat() call fails. @@ -548,35 +552,35 @@ class abstracted_fs(object): drwxrwxrwx 1 owner group 0 Aug 31 18:50 e-books -rw-rw-rw- 1 owner group 380 Sep 02 3:40 module.py """ - for file in listing: - try: - st = self.lstat(file) - except os.error: - if ignore_err: - continue - raise - perms = filemode(st.st_mode) # permissions - nlinks = st.st_nlink # number of links to inode - if not nlinks: # non-posix system, let's use a bogus value - nlinks = 1 - size = st.st_size # file size - uname = "owner" - gname = "group" + for node in listing: + perms = filemode(node.unixperms) # permissions + nlinks = 1 + size = node.content_length or 0L + uname = node.uuser + gname = node.ugroup # stat.st_mtime could fail (-1) if last mtime is too old # in which case we return the local time as last mtime try: - mname=_get_month_name(time.strftime("%m", time.localtime(st.st_mtime))) - mtime = mname+' '+time.strftime("%d %H:%M", time.localtime(st.st_mtime)) + st_mtime = node.write_date or 0.0 + if isinstance(st_mtime, basestring): + st_mtime = time.strptime(st_mtime, '%Y-%m-%d %H:%M:%S') + elif isinstance(st_mtime, float): + st_mtime = time.localtime(st_mtime) + mname=_get_month_name(time.strftime("%m", st_mtime )) + mtime = mname+' '+time.strftime("%d %H:%M", st_mtime) except ValueError: mname=_get_month_name(time.strftime("%m")) mtime = mname+' '+time.strftime("%d %H:%M") + fpath = node.path + if isinstance(fpath, (list, tuple)): + fpath = fpath[-1] # formatting is matched with proftpd ls output - path=_to_decode(file.path) #file.path.encode('ascii','replace').replace('?','_') + path=_to_decode(fpath) yield "%s %3s %-8s %-8s %8s %s %s\r\n" %(perms, nlinks, uname, gname, - size, mtime, path.split('/')[-1]) + size, mtime, path) # Ok - def format_mlsx(self, basedir, listing, perms, facts, ignore_err=True): + def format_mlsx(self, cr, basedir, listing, perms, facts, ignore_err=True): """Return an iterator object that yields the entries of a given directory or of a single file in a form suitable with MLSD and MLST commands. @@ -609,15 +613,9 @@ class abstracted_fs(object): if 'd' in perms: permdir += 'p' type = size = perm = modify = create = unique = mode = uid = gid = "" - for file in listing: - try: - st = self.stat(file) - except OSError: - if ignore_err: - continue - raise + for node in listing: # type + perm - if stat.S_ISDIR(st.st_mode): + if self.isdir(node): if 'type' in facts: type = 'type=dir;' if 'perm' in facts: @@ -628,29 +626,37 @@ class abstracted_fs(object): if 'perm' in facts: perm = 'perm=%s;' %permfile if 'size' in facts: - size = 'size=%s;' %st.st_size # file size + size = 'size=%s;' % (node.content_length or 0L) # last modification time if 'modify' in facts: try: - modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S", - time.localtime(st.st_mtime)) + st_mtime = node.write_date or 0.0 + if isinstance(st_mtime, basestring): + st_mtime = time.strptime(st_mtime, '%Y-%m-%d %H:%M:%S') + elif isinstance(st_mtime, float): + st_mtime = time.localtime(st_mtime) + modify = 'modify=%s;' %time.strftime("%Y%m%d%H%M%S", st_mtime) except ValueError: # stat.st_mtime could fail (-1) if last mtime is too old modify = "" if 'create' in facts: # on Windows we can provide also the creation time try: - create = 'create=%s;' %time.strftime("%Y%m%d%H%M%S", - time.localtime(st.st_ctime)) + st_ctime = node.create_date or 0.0 + if isinstance(st_ctime, basestring): + st_ctime = time.strptime(st_ctime, '%Y-%m-%d %H:%M:%S') + elif isinstance(st_mtime, float): + st_ctime = time.localtime(st_ctime) + create = 'create=%s;' %time.strftime("%Y%m%d%H%M%S",st_ctime) except ValueError: create = "" # UNIX only if 'unix.mode' in facts: - mode = 'unix.mode=%s;' %oct(st.st_mode & 0777) + mode = 'unix.mode=%s;' %oct(node.unixperms & 0777) if 'unix.uid' in facts: - uid = 'unix.uid=%s;' %st.st_uid + uid = 'unix.uid=%s;' % node.uuser if 'unix.gid' in facts: - gid = 'unix.gid=%s;' %st.st_gid + gid = 'unix.gid=%s;' % node.ugroup # We provide unique fact (see RFC-3659, chapter 7.5.2) on # posix platforms only; we get it by mixing st_dev and # st_ino values which should be enough for granting an @@ -659,10 +665,12 @@ class abstracted_fs(object): # Implementors who want to provide unique fact on other # platforms should use some platform-specific method (e.g. # on Windows NTFS filesystems MTF records could be used). - if 'unique' in facts: - unique = "unique=%x%x;" %(st.st_dev, st.st_ino) - path=_to_decode(file.path) - path = path and path.split('/')[-1] or None + # if 'unique' in facts: todo + # unique = "unique=%x%x;" %(st.st_dev, st.st_ino) + path = node.path + if isinstance (path, (list, tuple)): + path = path[-1] + path=_to_decode(path) yield "%s%s%s%s%s%s%s%s%s %s\r\n" %(type, size, perm, modify, create, mode, uid, gid, unique, path) diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index c02323dbc53..f92fbb34613 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -1138,19 +1138,11 @@ class AbstractedFS: # note: the following operations are no more blocking - def get_list_dir(self, path): + def get_list_dir(self, datacr): """"Return an iterator object that yields a directory listing in a form suitable for LIST command. """ - if self.isdir(path): - listing = self.listdir(path) - listing.sort() - return self.format_list(path, listing) - # if path is a file or a symlink we return information about it - else: - basedir, filename = os.path.split(path) - self.lstat(path) # raise exc in case of problems - return self.format_list(basedir, [filename]) + raise DeprecationWarning() def get_stat_dir(self, rawline): """Return an iterator object that yields a list of files @@ -1591,26 +1583,8 @@ class FTPHandler(asynchat.async_chat): def __check_path(self, cmd, line): """Check whether a path is valid.""" - # For the following commands we have to make sure that the real - # path destination belongs to the user's root directory. - # If provided path is a symlink we follow its final destination - # to do so. - if cmd in ('APPE','CWD','DELE','MDTM','NLST','MLSD','MLST','RETR', - 'RMD','SIZE','STOR','XCWD','XRMD'): - datacr = None - datacr = self.fs.get_cr(line) - try: - if not self.fs.validpath(self.fs.ftp2fs(line, datacr)): - line = self.fs.ftpnorm(line) - err = '"%s" points to a path which is outside ' \ - "the user's root directory" %line - self.respond("550 %s." %err) - self.log('FAIL %s "%s". %s.' %(cmd, line, err)) - self.fs.close_cr(datacr) - return False - except: - pass - self.fs.close_cr(datacr) + + # Always true, we will only check later, once we have a cursor return True def __check_perm(self, cmd, line, datacr): @@ -1623,6 +1597,7 @@ class FTPHandler(asynchat.async_chat): 'RNFR':'f', 'MKD':'m', 'XMKD':'m', 'STOR':'w'} + raise NotImplementedError if cmd in map: if cmd == 'STAT' and not line: return True @@ -2032,56 +2007,65 @@ class FTPHandler(asynchat.async_chat): # - Some older FTP clients erroneously issue /bin/ls-like LIST # formats in which case we fall back on cwd as default. if not line or line.lower() in ('-a', '-l', '-al', '-la'): - line = self.fs.cwd + line = '' + datacr = None try: - data = None - data = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, data) - line = self.fs.ftpnorm(line) - iterator = self.run_as_current_user(self.fs.get_list_dir, path) + datacr = self.fs.get_crdata(line, mode='list') + iterator = self.run_as_current_user(self.fs.get_list_dir, datacr) + except IOError, err: + self.fs.close_cr(datacr) + self.respond('550 %s.'% err.strerror) + return except OSError, err: - self.fs.close_cr(data) + self.fs.close_cr(datacr) why = _strerror(err) self.log('FAIL LIST "%s". %s.' %(line, why)) self.respond('550 %s.' %why) - else: - self.fs.close_cr(data) - self.log('OK LIST "%s". Transfer starting.' %line) + return + try: + self.log('OK LIST "%s". Transfer starting.' % line) producer = BufferedIteratorProducer(iterator) self.push_dtp_data(producer, isproducer=True) + finally: + self.fs.close_cr(datacr) + def ftp_NLST(self, line): """Return a list of files in the specified directory in a compact form to the client. """ if not line: - line = self.fs.cwd + line = '' + + datacr = None try: - data = None - data = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, data) - line = self.fs.ftpnorm(line) - if self.fs.isdir(path): - listing = self.run_as_current_user(self.fs.listdir, path) - listing = map(lambda x:os.path.split(x.path)[1], listing) + datacr = self.fs.get_crdata(line, mode='list') + if not datacr: + datacr = ( None, None, None ) + if self.fs.isdir(datacr[1]): + listing = self.run_as_current_user(self.fs.listdir, datacr) + listing = [ x.path[-1] for x in listing ] else: # if path is a file we just list its name - self.fs.lstat(path) # raise exc in case of problems - basedir, filename = os.path.split(line) - listing = [filename] + listing = [datacr[1].path[-1],] + except IOError, err: + self.fs.close_cr(datacr) + self.respond('550 %s.'% err.strerror) + return except OSError, err: - self.fs.close_cr(data) + self.fs.close_cr(datacr) why = _strerror(err) self.log('FAIL NLST "%s". %s.' %(line, why)) self.respond('550 %s.' %why) - else: - self.fs.close_cr(data) - data = '' - if listing: - listing.sort() - data = '\r\n'.join(listing) + '\r\n' - self.log('OK NLST "%s". Transfer starting.' %line) - self.push_dtp_data(data) + return + + self.fs.close_cr(datacr) + data = '' + if listing: + listing.sort() + data = '\r\n'.join(listing) + '\r\n' + self.log('OK NLST "%s". Transfer starting.' %line) + self.push_dtp_data(data) # --- MLST and MLSD commands @@ -2096,18 +2080,15 @@ class FTPHandler(asynchat.async_chat): """ # if no argument, fall back on cwd as default if not line: - line = self.fs.cwd + line = '' + datacr = None try: - datacr = None - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - basedir, basename = os.path.split(path) + datacr = self.fs.get_crdata(line, mode='list') perms = self.authorizer.get_perms(self.username) - iterator = self.run_as_current_user(self.fs.format_mlsx, basedir, - [basename], perms, self.current_facts, ignore_err=False) + iterator = self.run_as_current_user(self.fs.format_mlsx, datacr[0], datacr[1].parent, + [datacr[1],], perms, self.current_facts, ignore_err=False) data = ''.join(iterator) - except OSError, err: + except EnvironmentError, err: self.fs.close_cr(datacr) why = _strerror(err) self.log('FAIL MLST "%s". %s.' %(line, why)) @@ -2129,19 +2110,18 @@ class FTPHandler(asynchat.async_chat): """ # if no argument, fall back on cwd as default if not line: - line = self.fs.cwd + line = '' + + datacr = None try: - datacr = None - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) + datacr = self.fs.get_crdata(line, mode='list') # RFC-3659 requires 501 response code if path is not a directory - if not self.fs.isdir(path): + if not self.fs.isdir(datacr[1]): err = 'No such directory' self.log('FAIL MLSD "%s". %s.' %(line, err)) self.respond("501 %s." %err) return - listing = self.run_as_current_user(self.fs.listdir, path) + listing = self.run_as_current_user(self.fs.listdir, datacr) except OSError, err: self.fs.close_cr(datacr) why = _strerror(err) @@ -2150,7 +2130,7 @@ class FTPHandler(asynchat.async_chat): else: self.fs.close_cr(datacr) perms = self.authorizer.get_perms(self.username) - iterator = self.fs.format_mlsx(path, listing, perms, + iterator = self.fs.format_mlsx(datacr[0], datacr[1], listing, perms, self.current_facts) producer = BufferedIteratorProducer(iterator) self.log('OK MLSD "%s". Transfer starting.' %line) @@ -2160,12 +2140,10 @@ class FTPHandler(asynchat.async_chat): """Retrieve the specified file (transfer from the server to the client) """ + datacr = None try: - datacr = None - datacr = self.fs.get_cr(line) - file = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - fd = self.run_as_current_user(self.fs.open, file, 'rb') + datacr = self.fs.get_crdata(line, mode='file') + fd = self.run_as_current_user(self.fs.open, datacr, 'rb') except OSError, err: self.fs.close_cr(datacr) why = _strerror(err) @@ -2174,7 +2152,7 @@ class FTPHandler(asynchat.async_chat): return except IOError, err: self.fs.close_cr(datacr) - why = _strerror(err) + why = err.strerror self.log('FAIL RETR "%s". %s.' %(line, why)) self.respond('550 %s.' %why) return @@ -2187,7 +2165,7 @@ class FTPHandler(asynchat.async_chat): # the REST. ok = 0 try: - assert not self.restart_position > self.fs.getsize(file) + assert not self.restart_position > self.fs.getsize(datacr) fd.seek(self.restart_position) ok = 1 except AssertionError: @@ -2217,14 +2195,9 @@ class FTPHandler(asynchat.async_chat): else: cmd = 'STOR' - line = self.fs.ftpnorm(line) - basedir,basename = os.path.split(line) - datacr = None try: - datacr = self.fs.get_cr(line) - file = self.fs.ftp2fs(basedir, datacr) - + datacr = self.fs.get_crdata(line,mode='create') except OSError, err: self.fs.close_cr(datacr) why = _strerror(err) @@ -2235,7 +2208,7 @@ class FTPHandler(asynchat.async_chat): if self.restart_position: mode = 'r+' try: - fd = self.run_as_current_user(self.fs.create, file, basename, mode + 'b') + fd = self.run_as_current_user(self.fs.create, datacr, datacr[2], mode + 'b') except IOError, err: self.fs.close_cr(datacr) why = _strerror(err) @@ -2251,7 +2224,7 @@ class FTPHandler(asynchat.async_chat): # specified in the REST. ok = 0 try: - assert not self.restart_position > self.fs.getsize(self.fs.ftp2fs(line, datacr)) + assert not self.restart_position > self.fs.getsize(datacr) fd.seek(self.restart_position) ok = 1 except AssertionError: @@ -2293,15 +2266,12 @@ class FTPHandler(asynchat.async_chat): self.respond("450 Can't STOU while REST request is pending.") return - datacr = None - datacr = self.fs.get_cr(line) if line: - line = self.fs.ftpnorm(line) - basedir,prefix = os.path.split(line) - basedir = self.fs.ftp2fs(basedir, datacr) - #prefix = prefix + '.' + datacr = self.fs.get_crdata(line, mode='create') + # TODO else: + # TODO basedir = self.fs.ftp2fs(self.fs.cwd, datacr) prefix = 'ftpd.' try: @@ -2500,21 +2470,23 @@ class FTPHandler(asynchat.async_chat): def ftp_PWD(self, line): """Return the name of the current working directory to the client.""" - self.respond('257 "%s" is the current directory.' %self.fs.cwd) + cwd = self.fs.get_cwd() + self.respond('257 "%s" is the current directory.' % cwd) def ftp_CWD(self, line): """Change the current working directory.""" - # TODO: a lot of FTP servers go back to root directory if no + # check: a lot of FTP servers go back to root directory if no # arg is provided but this is not specified in RFC-959. # Search for official references about this behaviour. - if not line: - line = '/' datacr = None try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - self.run_as_current_user(self.fs.chdir, path) - except OSError, err: + datacr = self.fs.get_crdata(line,'cwd') + self.run_as_current_user(self.fs.chdir, datacr) + cwd = self.fs.get_cwd() + self.log('OK CWD "%s".' % cwd) + self.respond('250 "%s" is the current directory.' % cwd) + except EnvironmentError, err: + self.log("Could not cwd: %s" % err) if err.errno==2: why = 'Authentication Required or Failed' self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why)) @@ -2523,9 +2495,6 @@ class FTPHandler(asynchat.async_chat): why = _strerror(err) self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why)) self.respond('550 %s.' %why) - else: - self.log('OK CWD "%s".' %self.fs.cwd) - self.respond('250 "%s" is the current directory.' %self.fs.cwd) self.fs.close_cr(datacr) def ftp_CDUP(self, line): @@ -2554,17 +2523,15 @@ class FTPHandler(asynchat.async_chat): """ datacr = None try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - if self.fs.isdir(path): - why = "%s is not retrievable" %line - self.log('FAIL SIZE "%s". %s.' %(line, why)) - self.respond("550 %s." %why) - self.fs.close_cr(datacr) - return - size = self.run_as_current_user(self.fs.getsize, path) - except OSError, err: + datacr = self.fs.get_crdata(line, mode='file') + #if self.fs.isdir(datacr[1]): + # why = "%s is not retrievable" %line + # self.log('FAIL SIZE "%s". %s.' %(line, why)) + # self.respond("550 %s." %why) + # self.fs.close_cr(datacr) + # return + size = self.run_as_current_user(self.fs.getsize, datacr) + except EnvironmentError, err: why = _strerror(err) self.log('FAIL SIZE "%s". %s.' %(line, why)) self.respond('550 %s.' %why) @@ -2579,35 +2546,31 @@ class FTPHandler(asynchat.async_chat): """ datacr = None try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - if not self.fs.isfile(self.fs.realpath(path)): + datacr = self.fs.get_crdata(line) + if not self.fs.isfile(datacr): why = "%s is not retrievable" %line self.log('FAIL MDTM "%s". %s.' %(line, why)) self.respond("550 %s." %why) self.fs.close_cr(datacr) return - lmt = self.run_as_current_user(self.fs.getmtime, path) + lmt = self.run_as_current_user(self.fs.getmtime, datacr) + lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt)) + self.respond("213 %s" %lmt) + self.log('OK MDTM "%s".' %line) except OSError, err: why = _strerror(err) self.log('FAIL MDTM "%s". %s.' %(line, why)) self.respond('550 %s.' %why) - else: - lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt)) - self.respond("213 %s" %lmt) - self.log('OK MDTM "%s".' %line) self.fs.close_cr(datacr) def ftp_MKD(self, line): """Create the specified directory.""" - datacr = None - line = self.fs.ftpnorm(line) - basedir,basename = os.path.split(line) try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(basedir, datacr) - self.run_as_current_user(self.fs.mkdir, path, basename) + datacr = self.fs.get_crdata(line, mode='create') + self.run_as_current_user(self.fs.mkdir, datacr, datacr[2]) + except IOError, err: + self.log('FAIL MKD "%s". %s.' %(line, err.strerror)) + self.respond('550 %s.' % err.strerror) except OSError, err: why = _strerror(err) self.log('FAIL MKD "%s". %s.' %(line, why)) @@ -2621,16 +2584,14 @@ class FTPHandler(asynchat.async_chat): """Remove the specified directory.""" datacr = None try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - if self.fs.realpath(path) == self.fs.realpath(self.fs.root): + datacr = self.fs.get_crdata(line, mode='delete') + if not datacr[1]: msg = "Can't remove root directory." self.respond("550 %s" %msg) self.log('FAIL MKD "/". %s' %msg) self.fs.close_cr(datacr) return - self.run_as_current_user(self.fs.rmdir, path) + self.run_as_current_user(self.fs.rmdir, datacr) except OSError, err: why = _strerror(err) self.log('FAIL RMD "%s". %s.' %(line, why)) @@ -2644,10 +2605,8 @@ class FTPHandler(asynchat.async_chat): """Delete the specified file.""" datacr = None try: - datacr = self.fs.get_cr(line) - path = self.fs.ftp2fs(line, datacr) - line = self.fs.ftpnorm(line) - self.run_as_current_user(self.fs.remove, path) + datacr = self.fs.get_crdata(line, mode='delete') + self.run_as_current_user(self.fs.remove, datacr) except OSError, err: why = _strerror(err) self.log('FAIL DELE "%s". %s.' %(line, why)) @@ -2662,15 +2621,13 @@ class FTPHandler(asynchat.async_chat): here, see RNTO command)""" datacr = None try: - datacr = self.fs.get_cr(line) - line = self.fs.ftpnorm(line) - path = self.fs.ftp2fs(line, datacr) - if not self.fs.lexists(path): + datacr = self.fs.get_crdata(line, mode='rfnr') + if not datacr[1]: self.respond("550 No such file or directory.") - elif self.fs.realpath(path) == self.fs.realpath(self.fs.root): + elif not datacr[1]: self.respond("550 Can't rename the home directory.") else: - self.fs.rnfr = line + self.fs.rnfr = datacr[1] self.respond("350 Ready for destination name.") except: self.respond("550 Can't find the file or directory.") @@ -2686,21 +2643,17 @@ class FTPHandler(asynchat.async_chat): datacr = None try: try: - datacr = self.fs.get_cr(line) - src = self.fs.ftp2fs(self.fs.rnfr, datacr) - line = self.fs.ftpnorm(line) - basedir,basename = os.path.split(line) - dst = self.fs.ftp2fs(basedir, datacr) - self.run_as_current_user(self.fs.rename, src, dst,basename) - except OSError, err: + datacr = self.fs.get_crdata(line,'create') + oldname = '/'.join(self.fs.rnfr.path) + self.run_as_current_user(self.fs.rename, self.fs.rnfr, datacr) + self.fs.rnfr = None + self.log('OK RNFR/RNTO "%s ==> %s".' %(oldname, line)) + self.respond("250 Renaming ok.") + except EnvironmentError, err: why = _strerror(err) self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \ %(self.fs.ftpnorm(self.fs.rnfr), line, why)) self.respond('550 %s.' %why) - else: - self.log('OK RNFR/RNTO "%s ==> %s".' \ - %(self.fs.ftpnorm(self.fs.rnfr), line)) - self.respond("250 Renaming ok.") finally: self.fs.rnfr = None self.fs.close_cr(datacr) -- cgit v1.2.3 From 0828a06de49509334308597ffa1bda929dcf7460 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sat, 3 Jul 2010 00:53:02 +0300 Subject: Document ftp: don't close the cursor at the abstract_fs Since the get_crdata() has created cursors outside the scope of this function, it is not wise to close them inside. bzr revid: p_christ@hol.gr-20100702215302-y6fsu4kz05g1a9v7 --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 60974896b41..ae6ca935f64 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -249,8 +249,6 @@ class abstracted_fs(object): except Exception,e: self._log.exception('Cannot create dir "%s" at node %s', basename, repr(node)) raise OSError(1, 'Operation not permited.') - finally: - if cr: cr.close() def close_cr(self, data): if data and data[0]: -- cgit v1.2.3 From 606df7368c8fdea6bc54d787832069fbb2304822 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 6 Jul 2010 09:01:55 +0300 Subject: Document ftp: better handling of wrong paths. bzr revid: p_christ@hol.gr-20100706060155-nyljhua59ahax7sx --- addons/document_ftp/ftpserver/abstracted_fs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index ae6ca935f64..b5465c9873d 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -300,9 +300,11 @@ class abstracted_fs(object): if os.path.isabs(path): # we have to start from root, again p_parts = p_parts[1:] + if not p_parts: + raise IOError(errno.EPERM, 'Cannot perform operation at root dir') dbname = p_parts[0] if dbname not in self.db_list(): - return None + return IOError(errno.ENOENT,'Invalid database path') try: db = pooler.get_db(dbname) except Exception: -- cgit v1.2.3 From 61b09c7fc9be040308c7d1fdb73bed9818aa6827 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 6 Jul 2010 15:10:50 +0300 Subject: Document ftp: fix NLST, path resolution. bzr revid: p_christ@hol.gr-20100706121050-5u0i2hsm2phz0i30 --- addons/document_ftp/ftpserver/abstracted_fs.py | 12 ++++++++---- addons/document_ftp/ftpserver/ftpserver.py | 12 +++++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index b5465c9873d..6731908d1cf 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -275,8 +275,8 @@ class abstracted_fs(object): if not os.path.isabs(path): path = os.path.join(self.root, path) - if path == '/' and mode in ('list', 'cwd'): - return (None, None, None ) + if path == '/' and mode in ('list', 'cwd'): + return (None, None, None ) path = os.path.normpath(path) # again, for '/db/../ss' if path == '.': path = '' @@ -299,12 +299,14 @@ class abstracted_fs(object): if os.path.isabs(path): # we have to start from root, again - p_parts = p_parts[1:] + while p_parts[0] == '': + p_parts = p_parts[1:] + # self._log.debug("Path parts: %r ", p_parts) if not p_parts: raise IOError(errno.EPERM, 'Cannot perform operation at root dir') dbname = p_parts[0] if dbname not in self.db_list(): - return IOError(errno.ENOENT,'Invalid database path') + raise IOError(errno.ENOENT,'Invalid database path') try: db = pooler.get_db(dbname) except Exception: @@ -320,6 +322,7 @@ class abstracted_fs(object): raise OSError(2, 'Authentification Required.') n = get_node_context(cr, uid, {}) node = n.get_uri(cr, p_parts[1:]) + # self._log.debug("get_crdata(abs): %r" % ( (cr, node, rem_path),)) return (cr, node, rem_path) else: # we never reach here if cwd_node is not set @@ -333,6 +336,7 @@ class abstracted_fs(object): if node is False and mode not in ('???'): cr.close() raise IOError(errno.ENOENT, 'Path does not exist') + # self._log.debug("get_crdata(rel): %r" % ( (cr, node, rem_path),)) return (cr, node, rem_path) def get_node_cr_uid(self, node): diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index f92fbb34613..75ad5b00883 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -2043,11 +2043,17 @@ class FTPHandler(asynchat.async_chat): if not datacr: datacr = ( None, None, None ) if self.fs.isdir(datacr[1]): - listing = self.run_as_current_user(self.fs.listdir, datacr) - listing = [ x.path[-1] for x in listing ] + nodelist = self.run_as_current_user(self.fs.listdir, datacr) else: # if path is a file we just list its name - listing = [datacr[1].path[-1],] + nodelist = [datacr[1],] + + listing = [] + for nl in nodelist: + if isinstance(nl.path, (list, tuple)): + listing.append(nl.path[-1]) + else: + listing.append(nl.path) # assume string except IOError, err: self.fs.close_cr(datacr) self.respond('550 %s.'% err.strerror) -- cgit v1.2.3 From eb96e0f256b9130f0f1b814be6415d6b35a46d23 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 7 Jul 2010 02:29:28 +0300 Subject: Document ftp: solve the tests paradox the FTP server has been limited to only serve databases that have the document_ftp module "installed". However, this would break running the tests, because the module would be marked as "to upgrade". bzr revid: p_christ@hol.gr-20100706232928-5ckj97p61nfrq3rn --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 6731908d1cf..7667b7ee473 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -98,7 +98,7 @@ class abstracted_fs(object): if not cr.fetchone(): continue - cr.execute("SELECT id FROM ir_module_module WHERE name = 'document_ftp' AND state='installed' ") + cr.execute("SELECT id FROM ir_module_module WHERE name = 'document_ftp' AND state IN ('installed', 'to upgrade') ") res = cr.fetchone() if res and len(res): self.db_name_list.append(db_name) -- cgit v1.2.3 From e7d01ae1e35d3c7431ce430714ab878fe1fd8d0d Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 9 Jul 2010 01:52:13 +0300 Subject: Document FTP: fix walking utf8 paths. Also fix the error message when rename fails for utf8 filenames. bzr revid: p_christ@hol.gr-20100708225213-fcq2nbgectztjycq --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- addons/document_ftp/ftpserver/ftpserver.py | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 7667b7ee473..d2a5d08207f 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -278,7 +278,7 @@ class abstracted_fs(object): if path == '/' and mode in ('list', 'cwd'): return (None, None, None ) - path = os.path.normpath(path) # again, for '/db/../ss' + path = _to_unicode(os.path.normpath(path)) # again, for '/db/../ss' if path == '.': path = '' if os.path.isabs(path) and self.cwd_node is not None \ diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index 75ad5b00883..8d3fa4b0531 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -221,6 +221,17 @@ def _strerror(err): else: return err.strerror +def _to_unicode(s): + try: + return s.decode('utf-8') + except UnicodeError: + try: + return s.decode('latin') + except UnicodeError: + try: + return s.encode('ascii') + except UnicodeError: + return s # --- library defined exceptions @@ -2650,15 +2661,18 @@ class FTPHandler(asynchat.async_chat): try: try: datacr = self.fs.get_crdata(line,'create') - oldname = '/'.join(self.fs.rnfr.path) + oldname = self.fs.rnfr.path + if isinstance(oldname, (list, tuple)): + oldname = '/'.join(oldname) self.run_as_current_user(self.fs.rename, self.fs.rnfr, datacr) self.fs.rnfr = None - self.log('OK RNFR/RNTO "%s ==> %s".' %(oldname, line)) + self.log('OK RNFR/RNTO "%s ==> %s".' % \ + (_to_unicode(oldname), _to_unicode(line))) self.respond("250 Renaming ok.") except EnvironmentError, err: why = _strerror(err) self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \ - %(self.fs.ftpnorm(self.fs.rnfr), line, why)) + % (_to_unicode(oldname), _to_unicode(line), why)) self.respond('550 %s.' %why) finally: self.fs.rnfr = None -- cgit v1.2.3 From 796ed8e5987aa04cb2f4aa2cc950254363e29195 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 9 Jul 2010 01:52:17 +0300 Subject: Document FTP: fix typo inside error message. bzr revid: p_christ@hol.gr-20100708225217-ui4z1rc8gkuyynnl --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index d2a5d08207f..318957e68fa 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -416,7 +416,7 @@ class abstracted_fs(object): assert (ret is True) or (ret is False) cr.commit() except Exception,err: - self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, dst_basename, dst_basedir) + self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, datacr[2], datacr[1]) raise OSError(1,'Operation not permited.') def stat(self, node): -- cgit v1.2.3 From 173be44150448b48acc17a641640797c14aee237 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Fri, 9 Jul 2010 01:54:33 +0300 Subject: Document ftp: fix unicode in NLST At the nlst command, the paths from the API could have been unicode, in which case the '\r\n' separator would be sent as '\00\r\00\n' over the wire (that's utf-16 encoding). For that, bring the utf-8 conversion functions inside ftpserver. bzr revid: p_christ@hol.gr-20100708225433-fqpl911kqfsrm1ff --- addons/document_ftp/ftpserver/abstracted_fs.py | 25 ++------------------ addons/document_ftp/ftpserver/ftpserver.py | 32 +++++++++++++++++++------- 2 files changed, 26 insertions(+), 31 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 318957e68fa..4f0428ecb02 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -34,29 +34,8 @@ def _get_month_name(month): elif month==11:return 'Nov' elif month==12:return 'Dec' -def _to_unicode(s): - try: - return s.decode('utf-8') - except UnicodeError: - try: - return s.decode('latin') - except UnicodeError: - try: - return s.encode('ascii') - except UnicodeError: - return s - -def _to_decode(s): - try: - return s.encode('utf-8') - except UnicodeError: - try: - return s.encode('latin') - except UnicodeError: - try: - return s.decode('ascii') - except UnicodeError: - return s +from ftpserver import _to_decode, _to_unicode + class abstracted_fs(object): """A class used to interact with the file system, providing a high diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index 8d3fa4b0531..e267216ddec 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -225,13 +225,29 @@ def _to_unicode(s): try: return s.decode('utf-8') except UnicodeError: - try: - return s.decode('latin') - except UnicodeError: - try: - return s.encode('ascii') - except UnicodeError: - return s + pass + try: + return s.decode('latin') + except UnicodeError: + pass + try: + return s.encode('ascii') + except UnicodeError: + return s + +def _to_decode(s): + try: + return s.encode('utf-8') + except UnicodeError: + pass + try: + return s.encode('latin') + except UnicodeError: + pass + try: + return s.decode('ascii') + except UnicodeError: + return s # --- library defined exceptions @@ -2080,7 +2096,7 @@ class FTPHandler(asynchat.async_chat): data = '' if listing: listing.sort() - data = '\r\n'.join(listing) + '\r\n' + data = ''.join([ _to_decode(x) + '\r\n' for x in listing ]) self.log('OK NLST "%s". Transfer starting.' %line) self.push_dtp_data(data) -- cgit v1.2.3 From 0dedf035f9e4b2381e4a9cd5d412ca7a8c930b92 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Sun, 11 Jul 2010 17:25:48 +0300 Subject: Document ftp: allow non-latin user, group. bzr revid: p_christ@hol.gr-20100711142548-6uzoxs4224u8n2cf --- addons/document_ftp/ftpserver/abstracted_fs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 4f0428ecb02..565f003dacd 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -539,8 +539,8 @@ class abstracted_fs(object): perms = filemode(node.unixperms) # permissions nlinks = 1 size = node.content_length or 0L - uname = node.uuser - gname = node.ugroup + uname = _to_decode(node.uuser) + gname = _to_decode(node.ugroup) # stat.st_mtime could fail (-1) if last mtime is too old # in which case we return the local time as last mtime try: @@ -637,9 +637,9 @@ class abstracted_fs(object): if 'unix.mode' in facts: mode = 'unix.mode=%s;' %oct(node.unixperms & 0777) if 'unix.uid' in facts: - uid = 'unix.uid=%s;' % node.uuser + uid = 'unix.uid=%s;' % _to_decode(node.uuser) if 'unix.gid' in facts: - gid = 'unix.gid=%s;' % node.ugroup + gid = 'unix.gid=%s;' % _to_decode(node.ugroup) # We provide unique fact (see RFC-3659, chapter 7.5.2) on # posix platforms only; we get it by mixing st_dev and # st_ino values which should be enough for granting an -- cgit v1.2.3 From 8cd367f9ed6e49bc46c5dcccd2d52c5b9a6c4329 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 13 Jul 2010 13:30:35 +0300 Subject: Document FTP: fix open_data after 1e1118a2bd7ae After the revert, the node exception is TypeError (so that it is clearly distinguishable from any others). bzr revid: p_christ@hol.gr-20100713103035-usb1dbnma2l9c4ju --- addons/document_ftp/ftpserver/abstracted_fs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 565f003dacd..39899f2087f 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -169,7 +169,10 @@ class abstracted_fs(object): raise OSError(1, 'Operation not permited.') # Reading operation cr, node, rem = datacr - res = node.open_data(cr, mode) + try: + res = node.open_data(cr, mode) + except TypeError, e: + raise IOError(errno.EINVAL, "No data") return res # ok, but need test more -- cgit v1.2.3 From bbbf42c6ba0f5aa05a53c5b31f909432cbd5f708 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 13 Jul 2010 13:31:02 +0300 Subject: Document FTP: try to get meaningful length from files. This improves response from kio_ftp, because the SIZE request will no longer report 0 length. However, LIST etc may still report 0, which will confuse some clients. Solution is non-trivial, because the length can only be computed after the content is generated. bzr revid: p_christ@hol.gr-20100713103102-v5uou8uuqvqxm6yc --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 39899f2087f..ba135b23a01 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -430,7 +430,7 @@ class abstracted_fs(object): if not (datacr and datacr[1]): return 0L if datacr[1].type in ('file', 'content'): - return datacr[1].content_length or 0L + return datacr[1].get_data_len(datacr[0]) or 0L return 0L # Ok -- cgit v1.2.3 From d3ae0b008347718cec73df4523da0b674e2ba830 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 13 Jul 2010 23:53:36 +0300 Subject: Document FTP: after nodefd.open_data(), cursor must commit Because open_data, at the create case, updates the store_fname. bzr revid: p_christ@hol.gr-20100713205336-jodj31v722x81grb --- addons/document_ftp/ftpserver/abstracted_fs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index ba135b23a01..5e7c8c1b31c 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -150,6 +150,7 @@ class abstracted_fs(object): raise OSError(1, 'Operation not permited.') ret = child.open_data(cr, mode) + cr.commit() return ret except EnvironmentError: raise @@ -159,7 +160,9 @@ class abstracted_fs(object): try: child = node.create_child(cr, objname, data=None) - return child.open_data(cr, mode) + ret = child.open_data(cr, mode) + cr.commit() + return ret except Exception,e: self._log.exception('Cannot create item %s at node %s', objname, repr(node)) raise OSError(1, 'Operation not permited.') @@ -171,6 +174,7 @@ class abstracted_fs(object): cr, node, rem = datacr try: res = node.open_data(cr, mode) + cr.commit() except TypeError, e: raise IOError(errno.EINVAL, "No data") return res -- cgit v1.2.3 From a47be2ef0d72c5de183b9af1d44f33055c6c2dcc Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Tue, 13 Jul 2010 23:54:18 +0300 Subject: Document ftp: improve exception handling, size may return 404 bzr revid: p_christ@hol.gr-20100713205418-8oe6svsdvw2yy4jn --- addons/document_ftp/ftpserver/abstracted_fs.py | 4 +++- addons/document_ftp/ftpserver/ftpserver.py | 16 +++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 5e7c8c1b31c..ddd34e2a04d 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -163,6 +163,8 @@ class abstracted_fs(object): ret = child.open_data(cr, mode) cr.commit() return ret + except EnvironmentError: + raise except Exception,e: self._log.exception('Cannot create item %s at node %s', objname, repr(node)) raise OSError(1, 'Operation not permited.') @@ -432,7 +434,7 @@ class abstracted_fs(object): def getsize(self, datacr): """Return the size of the specified file in bytes.""" if not (datacr and datacr[1]): - return 0L + raise IOError(errno.ENOENT, "No such file or directory") if datacr[1].type in ('file', 'content'): return datacr[1].get_data_len(datacr[0]) or 0L return 0L diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index e267216ddec..528ce057ff9 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -2565,9 +2565,12 @@ class FTPHandler(asynchat.async_chat): # return size = self.run_as_current_user(self.fs.getsize, datacr) except EnvironmentError, err: - why = _strerror(err) + why = err.strerror self.log('FAIL SIZE "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) + if err.errno == errno.ENOENT: + self.respond("404 %s." % why) + else: + self.respond('550 %s.' % why) else: self.respond("213 %s" %size) self.log('OK SIZE "%s".' %line) @@ -2640,10 +2643,13 @@ class FTPHandler(asynchat.async_chat): try: datacr = self.fs.get_crdata(line, mode='delete') self.run_as_current_user(self.fs.remove, datacr) - except OSError, err: - why = _strerror(err) + except EnvironmentError, err: + why = err.strerror self.log('FAIL DELE "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) + if err.errno == errno.ENOENT: + self.respond('404 %s.' % why) + else: + self.respond('550 %s.' %why) else: self.log('OK DELE "%s".' %line) self.respond("250 File removed.") -- cgit v1.2.3 From 9e390e4d17530915d7aa580004a1ea4186acd907 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 14 Jul 2010 12:02:43 +0300 Subject: Document FTP: avoid operations at "/" bzr revid: p_christ@hol.gr-20100714090243-1ob78zjmfbtjsg2a --- addons/document_ftp/ftpserver/abstracted_fs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index ddd34e2a04d..9b0f5b562e4 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -287,7 +287,7 @@ class abstracted_fs(object): if os.path.isabs(path): # we have to start from root, again - while p_parts[0] == '': + while p_parts and p_parts[0] == '': p_parts = p_parts[1:] # self._log.debug("Path parts: %r ", p_parts) if not p_parts: -- cgit v1.2.3 From 5a5608d71b2b4188adbd0d4d004f2a280d8e3388 Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 14 Jul 2010 15:52:28 +0300 Subject: Document FTP: restructure the function calling, more consistent exceptions. Hopefully, FTP now will behave better to clients like Firefox. It is better to pass the node-API messages to the FTP layer, than always return 550. Conflicts: document_ftp/ftpserver/ftpserver.py bzr revid: p_christ@hol.gr-20100714125228-7xs3zbi72may4198 --- addons/document_ftp/ftpserver/ftpserver.py | 307 +++++++++++++++-------------- 1 file changed, 156 insertions(+), 151 deletions(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index 528ce057ff9..88e48bf31bf 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -1354,6 +1354,11 @@ class AbstractedFS: # --- FTP +class FTPExceptionSent(Exception): + """An FTP exception that FTPHandler has processed + """ + pass + class FTPHandler(asynchat.async_chat): """Implements the FTP server Protocol Interpreter (see RFC-959), handling commands received from the client on the control channel. @@ -1453,6 +1458,13 @@ class FTPHandler(asynchat.async_chat): self._epsvall = False self.__in_dtp_queue = None self.__out_dtp_queue = None + + self.__errno_responses = { + errno.EPERM: 553, + errno.EINVAL: 504, + errno.ENOENT: 550, + errno.EREMOTE: 450, + } # mlsx facts attributes self.current_facts = ['type', 'perm', 'size', 'modify'] @@ -1815,6 +1827,58 @@ class FTPHandler(asynchat.async_chat): # --- connection + def try_as_current_user(self, function, args=None, kwargs=None, line=None, errno_resp=None): + """run function as current user, auto-respond in exceptions + @param args,kwargs the arguments, in list and dict respectively + @param errno_resp a dictionary of responses to IOError, OSError + """ + if errno_resp: + eresp = self.__errno_responses.copy() + eresp.update(errno_resp) + else: + eresp = self.__errno_responses + + uline = '' + if line: + uline = ' "%s"' % _to_unicode(line) + try: + if args is None: + args = () + if kwargs is None: + kwargs = {} + return self.run_as_current_user(function, *args, **kwargs) + except NotImplementedError, err: + cmdname = function.__name__ + why = err.args[0] or 'Not implemented' + self.log('FAIL %s() not implemented: %s.' %(cmdname, why)) + self.respond('502 %s.' %why) + raise FTPExceptionSent(why) + except EnvironmentError, err: + cmdname = function.__name__ + try: + logline(traceback.format_exc()) + except Exception: + pass + ret_code = eresp.get(err.errno, '451') + why = (err.strerror) or 'Error in command' + self.log('FAIL %s() %s errno=%s: %s.' %(cmdname, uline, err.errno, why)) + self.respond('%s %s.' % (str(ret_code), why)) + + raise FTPExceptionSent(why) + except Exception, e: + cmdname = function.__name__ + try: + logerror(traceback.format_exc()) + except Exception: + pass + why = (err.args and err.args[0]) or 'Exception' + self.log('FAIL %s() %s Exception: %s.' %(cmdname, uline, why)) + self.respond('451 %s.' % why) + raise FTPExceptionSent(why) + + def get_crdata2(self, *args, **kwargs): + return self.try_as_current_user(self.fs.get_crdata, args, kwargs, line=args[0]) + def _make_eport(self, ip, port): """Establish an active data channel with remote client which issued a PORT or EPRT command. @@ -2037,18 +2101,12 @@ class FTPHandler(asynchat.async_chat): line = '' datacr = None try: - datacr = self.fs.get_crdata(line, mode='list') - iterator = self.run_as_current_user(self.fs.get_list_dir, datacr) - except IOError, err: + datacr = self.get_crdata2(line, mode='list') + iterator = self.try_as_current_user(self.fs.get_list_dir, (datacr,)) + except FTPExceptionSent: self.fs.close_cr(datacr) - self.respond('550 %s.'% err.strerror) - return - except OSError, err: - self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL LIST "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) return + try: self.log('OK LIST "%s". Transfer starting.' % line) producer = BufferedIteratorProducer(iterator) @@ -2066,11 +2124,11 @@ class FTPHandler(asynchat.async_chat): datacr = None try: - datacr = self.fs.get_crdata(line, mode='list') + datacr = self.get_crdata2(line, mode='list') if not datacr: datacr = ( None, None, None ) if self.fs.isdir(datacr[1]): - nodelist = self.run_as_current_user(self.fs.listdir, datacr) + nodelist = self.try_as_current_user(self.fs.listdir, (datacr,)) else: # if path is a file we just list its name nodelist = [datacr[1],] @@ -2081,15 +2139,8 @@ class FTPHandler(asynchat.async_chat): listing.append(nl.path[-1]) else: listing.append(nl.path) # assume string - except IOError, err: - self.fs.close_cr(datacr) - self.respond('550 %s.'% err.strerror) - return - except OSError, err: + except FTPExceptionSent: self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL NLST "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) return self.fs.close_cr(datacr) @@ -2116,16 +2167,14 @@ class FTPHandler(asynchat.async_chat): line = '' datacr = None try: - datacr = self.fs.get_crdata(line, mode='list') + datacr = self.get_crdata2(line, mode='list') perms = self.authorizer.get_perms(self.username) - iterator = self.run_as_current_user(self.fs.format_mlsx, datacr[0], datacr[1].parent, - [datacr[1],], perms, self.current_facts, ignore_err=False) + iterator = self.try_as_current_user(self.fs.format_mlsx, (datacr[0], datacr[1].parent, + [datacr[1],], perms, self.current_facts), {'ignore_err':False}) data = ''.join(iterator) - except EnvironmentError, err: + except FTPExceptionSent: self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL MLST "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) + return else: self.fs.close_cr(datacr) # since TVFS is supported (see RFC-3659 chapter 6), a fully @@ -2147,19 +2196,17 @@ class FTPHandler(asynchat.async_chat): datacr = None try: - datacr = self.fs.get_crdata(line, mode='list') + datacr = self.get_crdata2(line, mode='list') # RFC-3659 requires 501 response code if path is not a directory if not self.fs.isdir(datacr[1]): err = 'No such directory' self.log('FAIL MLSD "%s". %s.' %(line, err)) self.respond("501 %s." %err) return - listing = self.run_as_current_user(self.fs.listdir, datacr) - except OSError, err: + listing = self.try_as_current_user(self.fs.listdir, (datacr,)) + except FTPExceptionSent: self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL MLSD "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) + return else: self.fs.close_cr(datacr) perms = self.authorizer.get_perms(self.username) @@ -2175,19 +2222,10 @@ class FTPHandler(asynchat.async_chat): """ datacr = None try: - datacr = self.fs.get_crdata(line, mode='file') - fd = self.run_as_current_user(self.fs.open, datacr, 'rb') - except OSError, err: + datacr = self.get_crdata2(line, mode='file') + fd = self.try_as_current_user(self.fs.open, (datacr, 'rb')) + except FTPExceptionSent: self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL RETR "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) - return - except IOError, err: - self.fs.close_cr(datacr) - why = err.strerror - self.log('FAIL RETR "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) return if self.restart_position: @@ -2230,23 +2268,12 @@ class FTPHandler(asynchat.async_chat): datacr = None try: - datacr = self.fs.get_crdata(line,mode='create') - except OSError, err: - self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL %s "%s". %s.' %(cmd, line, why)) - self.respond('550 %s.' %why) - return - - if self.restart_position: - mode = 'r+' - try: - fd = self.run_as_current_user(self.fs.create, datacr, datacr[2], mode + 'b') - except IOError, err: + datacr = self.get_crdata2(line,mode='create') + if self.restart_position: + mode = 'r+' + fd = self.try_as_current_user(self.fs.create, (datacr, datacr[2], mode + 'b')) + except FTPExceptionSent: self.fs.close_cr(datacr) - why = _strerror(err) - self.log('FAIL %s "%s". %s.' %(cmd, line, why)) - self.respond('550 %s.' %why) return if self.restart_position: @@ -2301,16 +2328,19 @@ class FTPHandler(asynchat.async_chat): if line: - datacr = self.fs.get_crdata(line, mode='create') - # TODO + datacr = self.get_crdata2(line, mode='create') + # TODO else: - # TODO + # TODO basedir = self.fs.ftp2fs(self.fs.cwd, datacr) prefix = 'ftpd.' try: - fd = self.run_as_current_user(self.fs.mkstemp, prefix=prefix, - dir=basedir) - except IOError, err: + fd = self.try_as_current_user(self.fs.mkstemp, kwargs={'prefix':prefix, + 'dir': basedir}, line=line ) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return + except IOError, err: # TODO # hitted the max number of tries to find out file with # unique name if err.errno == errno.EEXIST: @@ -2513,22 +2543,15 @@ class FTPHandler(asynchat.async_chat): # Search for official references about this behaviour. datacr = None try: - datacr = self.fs.get_crdata(line,'cwd') - self.run_as_current_user(self.fs.chdir, datacr) + datacr = self.get_crdata2(line,'cwd') + self.try_as_current_user(self.fs.chdir, (datacr,), line=line, errno_resp={2: 530}) cwd = self.fs.get_cwd() self.log('OK CWD "%s".' % cwd) self.respond('250 "%s" is the current directory.' % cwd) - except EnvironmentError, err: - self.log("Could not cwd: %s" % err) - if err.errno==2: - why = 'Authentication Required or Failed' - self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why)) - self.respond('530 %s.' %why) - else: - why = _strerror(err) - self.log('FAIL CWD "%s". %s.' %(self.fs.ftpnorm(line), why)) - self.respond('550 %s.' %why) - self.fs.close_cr(datacr) + except FTPExceptionSent: + return + finally: + self.fs.close_cr(datacr) def ftp_CDUP(self, line): """Change into the parent directory.""" @@ -2556,21 +2579,17 @@ class FTPHandler(asynchat.async_chat): """ datacr = None try: - datacr = self.fs.get_crdata(line, mode='file') + datacr = self.get_crdata2(line, mode='file') #if self.fs.isdir(datacr[1]): # why = "%s is not retrievable" %line # self.log('FAIL SIZE "%s". %s.' %(line, why)) # self.respond("550 %s." %why) # self.fs.close_cr(datacr) # return - size = self.run_as_current_user(self.fs.getsize, datacr) - except EnvironmentError, err: - why = err.strerror - self.log('FAIL SIZE "%s". %s.' %(line, why)) - if err.errno == errno.ENOENT: - self.respond("404 %s." % why) - else: - self.respond('550 %s.' % why) + size = self.try_as_current_user(self.fs.getsize,(datacr,), line=line) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return else: self.respond("213 %s" %size) self.log('OK SIZE "%s".' %line) @@ -2581,36 +2600,35 @@ class FTPHandler(asynchat.async_chat): 3307 style timestamp (YYYYMMDDHHMMSS) as defined in RFC-3659. """ datacr = None + try: - datacr = self.fs.get_crdata(line) - if not self.fs.isfile(datacr): - why = "%s is not retrievable" %line - self.log('FAIL MDTM "%s". %s.' %(line, why)) - self.respond("550 %s." %why) - self.fs.close_cr(datacr) - return - lmt = self.run_as_current_user(self.fs.getmtime, datacr) + if line.find('/', 1) < 0: + # root or db, just return local + lmt = None + else: + datacr = self.get_crdata2(line) + if not datacr: + raise IOError(errno.ENOENT, "%s is not retrievable" %line) + #if not self.fs.isfile(datacr[1]): + # raise IOError(errno.EPERM, "%s is not a regular file" % line) + + lmt = self.try_as_current_user(self.fs.getmtime, (datacr,), line=line) lmt = time.strftime("%Y%m%d%H%M%S", time.localtime(lmt)) self.respond("213 %s" %lmt) self.log('OK MDTM "%s".' %line) - except OSError, err: - why = _strerror(err) - self.log('FAIL MDTM "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) - self.fs.close_cr(datacr) + except FTPExceptionSent: + return + finally: + self.fs.close_cr(datacr) def ftp_MKD(self, line): """Create the specified directory.""" try: - datacr = self.fs.get_crdata(line, mode='create') - self.run_as_current_user(self.fs.mkdir, datacr, datacr[2]) - except IOError, err: - self.log('FAIL MKD "%s". %s.' %(line, err.strerror)) - self.respond('550 %s.' % err.strerror) - except OSError, err: - why = _strerror(err) - self.log('FAIL MKD "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) + datacr = self.get_crdata2(line, mode='create') + self.try_as_current_user(self.fs.mkdir, (datacr, datacr[2]), line=line) + except FTPExceptionSent: + self.fs.close_cr(datacr) + return else: self.log('OK MKD "%s".' %line) self.respond("257 Directory created.") @@ -2620,39 +2638,30 @@ class FTPHandler(asynchat.async_chat): """Remove the specified directory.""" datacr = None try: - datacr = self.fs.get_crdata(line, mode='delete') + datacr = self.get_crdata2(line, mode='delete') if not datacr[1]: msg = "Can't remove root directory." - self.respond("550 %s" %msg) + self.respond("553 %s" %msg) self.log('FAIL MKD "/". %s' %msg) self.fs.close_cr(datacr) return - self.run_as_current_user(self.fs.rmdir, datacr) - except OSError, err: - why = _strerror(err) - self.log('FAIL RMD "%s". %s.' %(line, why)) - self.respond('550 %s.' %why) - else: + self.try_as_current_user(self.fs.rmdir, (datacr,), line=line) self.log('OK RMD "%s".' %line) self.respond("250 Directory removed.") + except FTPExceptionSent: + pass self.fs.close_cr(datacr) def ftp_DELE(self, line): """Delete the specified file.""" datacr = None try: - datacr = self.fs.get_crdata(line, mode='delete') - self.run_as_current_user(self.fs.remove, datacr) - except EnvironmentError, err: - why = err.strerror - self.log('FAIL DELE "%s". %s.' %(line, why)) - if err.errno == errno.ENOENT: - self.respond('404 %s.' % why) - else: - self.respond('550 %s.' %why) - else: + datacr = self.get_crdata2(line, mode='delete') + self.try_as_current_user(self.fs.remove, (datacr,), line=line) self.log('OK DELE "%s".' %line) self.respond("250 File removed.") + except FTPExceptionSent: + pass self.fs.close_cr(datacr) def ftp_RNFR(self, line): @@ -2660,16 +2669,16 @@ class FTPHandler(asynchat.async_chat): here, see RNTO command)""" datacr = None try: - datacr = self.fs.get_crdata(line, mode='rfnr') + datacr = self.get_crdata2(line, mode='rfnr') if not datacr[1]: self.respond("550 No such file or directory.") elif not datacr[1]: - self.respond("550 Can't rename the home directory.") + self.respond("553 Can't rename the home directory.") else: self.fs.rnfr = datacr[1] self.respond("350 Ready for destination name.") - except: - self.respond("550 Can't find the file or directory.") + except FTPExceptionSent: + pass self.fs.close_cr(datacr) def ftp_RNTO(self, line): @@ -2681,21 +2690,17 @@ class FTPHandler(asynchat.async_chat): return datacr = None try: - try: - datacr = self.fs.get_crdata(line,'create') - oldname = self.fs.rnfr.path - if isinstance(oldname, (list, tuple)): - oldname = '/'.join(oldname) - self.run_as_current_user(self.fs.rename, self.fs.rnfr, datacr) - self.fs.rnfr = None - self.log('OK RNFR/RNTO "%s ==> %s".' % \ - (_to_unicode(oldname), _to_unicode(line))) - self.respond("250 Renaming ok.") - except EnvironmentError, err: - why = _strerror(err) - self.log('FAIL RNFR/RNTO "%s ==> %s". %s.' \ - % (_to_unicode(oldname), _to_unicode(line), why)) - self.respond('550 %s.' %why) + datacr = self.get_crdata2(line,'create') + oldname = self.fs.rnfr.path + if isinstance(oldname, (list, tuple)): + oldname = '/'.join(oldname) + self.try_as_current_user(self.fs.rename, (self.fs.rnfr, datacr), line=line) + self.fs.rnfr = None + self.log('OK RNFR/RNTO "%s ==> %s".' % \ + (_to_unicode(oldname), _to_unicode(line))) + self.respond("250 Renaming ok.") + except FTPExceptionSent: + pass finally: self.fs.rnfr = None self.fs.close_cr(datacr) @@ -2784,9 +2789,9 @@ class FTPHandler(asynchat.async_chat): datacr = None try: datacr = self.fs.get_cr(line) - iterator = self.run_as_current_user(self.fs.get_stat_dir, line, datacr) - except OSError, err: - self.respond('550 %s.' %_strerror(err)) + iterator = self.try_as_current_user(self.fs.get_stat_dir, (line, datacr), line=line) + except FTPExceptionSent: + pass else: self.push('213-Status of "%s":\r\n' %self.fs.ftpnorm(line)) self.push_with_producer(BufferedIteratorProducer(iterator)) -- cgit v1.2.3 From 6cb5fa01edee3434f59654c2bf6365586702830b Mon Sep 17 00:00:00 2001 From: "P. Christeas" Date: Wed, 14 Jul 2010 15:52:32 +0300 Subject: Document, FTP: handle case of move into existing name. Unlike unix 'mv', we do not allow move to point to an existing 'new_name'. bzr revid: p_christ@hol.gr-20100714125232-fmm9sm1nc30nz92k --- addons/document_ftp/ftpserver/abstracted_fs.py | 4 +++- addons/document_ftp/ftpserver/ftpserver.py | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver') diff --git a/addons/document_ftp/ftpserver/abstracted_fs.py b/addons/document_ftp/ftpserver/abstracted_fs.py index 9b0f5b562e4..2ddfcc2bb7d 100644 --- a/addons/document_ftp/ftpserver/abstracted_fs.py +++ b/addons/document_ftp/ftpserver/abstracted_fs.py @@ -403,7 +403,9 @@ class abstracted_fs(object): # API shouldn't wait for us to write the object assert (ret is True) or (ret is False) cr.commit() - except Exception,err: + except EnvironmentError: + raise + except Exception, err: self._log.exception('Cannot rename "%s" to "%s" at "%s"', src, datacr[2], datacr[1]) raise OSError(1,'Operation not permited.') diff --git a/addons/document_ftp/ftpserver/ftpserver.py b/addons/document_ftp/ftpserver/ftpserver.py index 88e48bf31bf..692abc4aef4 100644 --- a/addons/document_ftp/ftpserver/ftpserver.py +++ b/addons/document_ftp/ftpserver/ftpserver.py @@ -1464,6 +1464,7 @@ class FTPHandler(asynchat.async_chat): errno.EINVAL: 504, errno.ENOENT: 550, errno.EREMOTE: 450, + errno.EEXIST: 521, } # mlsx facts attributes -- cgit v1.2.3