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/ftpserver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py | 275 ++++++++++++----------------- 1 file changed, 114 insertions(+), 161 deletions(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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 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/ftpserver.py') 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/ftpserver.py | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/document_ftp/ftpserver/ftpserver.py') 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