E5Network/E5Ftp.py

changeset 2074
5cb87968aad5
child 2077
68a34718a0ce
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/E5Network/E5Ftp.py	Sat Sep 29 18:37:03 2012 +0200
@@ -0,0 +1,321 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2012 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module implementing an extension to the Python FTP class to support FTP proxies.
+"""
+
+import ftplib
+from socket import _GLOBAL_DEFAULT_TIMEOUT
+
+
+class E5FtpProxyError(ftplib.Error):
+    """
+    Class to signal an error related to proxy configuration.
+    
+    The error message starts with a three digit error code followed by a
+    space and the error string. Supported error codes are:
+    <ul>
+      <li>910: proxy error; the second number gives the category of the proxy error.
+          The original response from the proxy is appended in the next line.</li>
+      <li>930: proxy error; the second number gives the category of the proxy error.
+          The original response from the proxy is appended in the next line.</li>
+      <li>940: proxy error; the second number gives the category of the proxy error.
+          The original response from the proxy is appended in the next line.</li>
+      <li>950: proxy error; the second number gives the category of the proxy error.
+          The original response from the proxy is appended in the next line.</li>
+      <li>990: proxy usage is enabled but no proxy host given</li>
+      <li>991: proxy usage is enabled but no proxy user given</li>
+      <li>992: proxy usage is enabled but no proxy password given</li>
+    </ul>
+    """
+    pass
+
+
+class E5FtpProxyType(object):
+    """
+    Class defining the supported FTP proxy types.
+    """
+    NoProxy = 0                     # no proxy
+    NonAuthorizing = 1              # non authorizing proxy
+    UserAtServer = 2                # proxy login first, than user@remote.host
+    Site = 3                        # proxy login first, than use SITE command
+    Open = 4                        # proxy login first, than use OPEN command
+    UserAtProxyuserAtServer = 5     # one login for both
+    ProxyuserAtServer = 6           # proxy login with remote host given, than
+                                    # normal remote login
+    AuthResp = 7                    # authenticate to proxy with AUTH and RESP commands
+    Bluecoat = 8                    # bluecoat proxy
+
+
+class E5Ftp(ftplib.FTP):
+    """
+    Class implementing an extension to the Python FTP class to support FTP proxies.
+    """
+    def __init__(self, host="", user="", password="", acct="",
+        proxyType=E5FtpProxyType.NoProxy, proxyHost="", proxyPort=ftplib.FTP_PORT,
+        proxyUser="", proxyPassword="", proxyAccount="",
+        timeout=_GLOBAL_DEFAULT_TIMEOUT):
+        """
+        Constructor
+        
+        @param host name of the FTP host (string)
+        @param user user name for login to FTP host (string)
+        @param password password for login to FTP host (string)
+        @param acct account for login to FTP host (string)
+        @param proxyType type of the FTP proxy (integer 0 to 8)
+        @param proxyHost name of the FTP proxy (string)
+        @param proxyPort port of the FTP proxy (integer)
+        @param proxyUser user name for login to the proxy (string)
+        @param proxyPassword password for login to the proxy (string)
+        @param proxyAccount accounting info for the proxy (string)
+        @param timeout timeout in seconds for blocking operations (integer)
+        """
+        self.__timeout = timeout
+        
+        self.__proxyType = proxyType
+        self.__proxyHost = proxyHost
+        self.__proxyPort = proxyPort
+        self.__proxyUser = proxyUser
+        self.__proxyPassword = proxyPassword
+        self.__proxyAccount = proxyAccount
+        
+        self.__host = host
+        self.__port = ftplib.FTP_PORT
+        self.__user = user
+        self.__password = password
+        self.__acct = acct
+        
+        if host:
+            self.connect(host)
+            if user:
+                self.login(user, password, acct)
+    
+    def setProxy(self, proxyType=E5FtpProxyType.NoProxy, proxyHost="",
+        proxyPort=ftplib.FTP_PORT, proxyUser="", proxyPassword="", proxyAccount=""):
+        """
+        Public method to set the proxy configuration.
+        
+        @param proxyType type of the FTP proxy (integer 0 to 8)
+        @param proxyHost name of the FTP proxy (string)
+        @param proxyPort port of the FTP proxy (integer)
+        @param proxyUser user name for login to the proxy (string)
+        @param proxyPassword password  for login to the proxy (string)
+        @param proxyAccount accounting info for the proxy (string)
+        """
+        self.__proxyType = proxyType
+        self.__proxyHost = proxyHost
+        self.__proxyPort = proxyPort
+        self.__proxyUser = proxyUser
+        self.__proxyPassword = proxyPassword
+        self.__proxyAccount = proxyAccount
+    
+    def setProxyAuthentication(self, proxyUser="", proxyPassword="", proxyAccount=""):
+        """
+        Public method to set the proxy authentication info.
+        
+        @param proxyUser user name for login to the proxy (string)
+        @param proxyPassword password  for login to the proxy (string)
+        @param proxyAccount accounting info for the proxy (string)
+        """
+        self.__proxyUser = proxyUser
+        self.__proxyPassword = proxyPassword
+        self.__proxyAccount = proxyAccount
+    
+    def connect(self, host="", port=0, timeout=-999):
+        """
+        Public method to connect to the given FTP server.
+        
+        This extended method connects to the proxy instead of the given host,
+        if a proxy is to be used. It throws an exception, if the proxy data
+        is incomplete.
+        
+        @param host name of the FTP host (string)
+        @param port port of the FTP host (integer)
+        @param timeout timeout in seconds for blocking operations (integer)
+        @return welcome message of the server (string)
+        @exception E5FtpProxyError raised to indicate a proxy related issue
+        """
+        if host:
+            self.__host = host
+        if port:
+            self.__port = port
+        if timeout != -999:
+            self.__timeout = timeout
+        
+        if self.__proxyType != E5FtpProxyType.NoProxy:
+            if not self.__proxyHost:
+                raise E5FtpProxyError(
+                    "990 Proxy usage requested, but no proxy host given.")
+            
+            return super().connect(self.__proxyHost, self.__proxyPort, self.__timeout)
+        else:
+            return super().connect(self.__host, self.__port, self.__timeout)
+    
+    def login(self, user="", password="", acct=""):
+        """
+        Public method to login to the FTP server.
+        
+        This extended method respects the FTP proxy configuration. There are many
+        different FTP proxy products available. But unfortunately there is no
+        standard for how o traverse a FTP proxy. The lis below shows the sequence
+        of commands used.
+        
+        <table>
+          <tr><td>user</td><td>Username for remote host</td></tr>
+          <tr><td>pass</td><td>Password for remote host</td></tr>
+          <tr><td>pruser</td><td>Username for FTP proxy</td></tr>
+          <tr><td>prpass</td><td>Password for FTP proxy</td></tr>
+          <tr><td>remote.host</td><td>Hostname of the remote FTP server</td></tr>
+        </table>
+        
+        <dl>
+          <dt>E5FtpProxyType.NoProxy</dt>
+          <dd>
+            USER user<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.NonAuthorizing</dt>
+          <dd>
+            USER user@remote.host<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.UserAtServer</dt>
+          <dd>
+            USER pruser<br/>
+            PASS prpass<br/>
+            USER user@remote.host<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.Site</dt>
+          <dd>
+            USER pruser<br/>
+            PASS prpass<br/>
+            SITE remote.site<br/>
+            USER user<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.Open</dt>
+          <dd>
+            USER pruser<br/>
+            PASS prpass<br/>
+            OPEN remote.site<br/>
+            USER user<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.UserAtProxyuserAtServer</dt>
+          <dd>
+            USER user@pruser@remote.host<br/>
+            PASS pass@prpass
+          </dd>
+          <dt>E5FtpProxyType.ProxyuserAtServer</dt>
+          <dd>
+            USER pruser@remote.host<br/>
+            PASS prpass<br/>
+            USER user<br/>
+            PASS pass
+          </dd>
+          <dt>E5FtpProxyType.AuthResp</dt>
+          <dd>
+            USER user@remote.host<br/>
+            PASS pass<br/>
+            AUTH pruser<br/>
+            RESP prpass
+          </dd>
+          <dt>E5FtpProxyType.Bluecoat</dt>
+          <dd>
+            USER user@remote.host pruser<br/>
+            PASS pass<br/>
+            ACCT prpass
+          </dd>
+        </dl>
+        
+        @param user username for the remote host (string)
+        @param password password for the remote host (string)
+        @param acct accounting information for the remote host (string)
+        @return response sent by the remote host (string)
+        @exception E5FtpProxyError raised to indicate a proxy related issue
+        """
+        if not user:
+            user = "anonymous"
+        if not password:
+            # make sure it is a string
+            password = ""
+        if not acct:
+            # make sure it is a string
+            acct = ""
+        if user == "anonymous" and password in {'', '-'}:
+            password += "anonymous@"
+        
+        if self.__proxyType != E5FtpProxyType.NoProxy:
+            if self.__proxyType != E5FtpProxyType.NonAuthorizing:
+                # check, if a valid proxy configuration is known
+                if not self.__proxyUser:
+                    raise E5FtpProxyError(
+                        "991 Proxy usage requested, but no proxy user given")
+                if not self.__proxyPassword:
+                    raise E5FtpProxyError(
+                        "992 Proxy usage requested, but no proxy password given")
+            
+            if self.__proxyType in [E5FtpProxyType.NonAuthorizing,
+                    E5FtpProxyType.AuthResp, E5FtpProxyType.Bluecoat]:
+                user += "@" + self.__host
+                if self.__proxyType == E5FtpProxyType.Bluecoat:
+                    user += " " + self.__proxyUser
+                    acct = self.__proxyPassword
+            elif self.__proxyType == E5FtpProxyType.UserAtProxyuserAtServer:
+                user = "{0}@{1}@{2}".format(user, self.__proxyUser, self.__host)
+                password = "{0}@{1}".format(password, self.__proxyPassword)
+            else:
+                pruser = self.__proxyUser
+                if self.__proxyType == E5FtpProxyType.UserAtServer:
+                    user += "@" + self.__host
+                elif self.__proxyType == E5FtpProxyType.ProxyuserAtServer:
+                    pruser += "@" + self.__host
+                
+                # authenticate to the proxy first
+                presp = self.sendcmd("USER " + pruser)
+                if presp[0] == "3":
+                    presp = self.sendcmd("PASS " + self.__proxyPassword)
+                if presp[0] == "3" and self.__proxyAccount:
+                    presp = self.sendcmd("ACCT " + self.__proxyAccount)
+                if presp[0] != "2":
+                    raise E5FtpProxyError("9{0}0 Error authorizing at proxy\n{1}".format(
+                        presp[0], presp))
+                
+                if self.__proxyType == E5FtpProxyType.Site:
+                    # send SITE command
+                    presp = self.sendcmd("SITE " + self.__host)
+                    if presp[0] != "2":
+                        raise E5FtpProxyError(
+                            "9{0}0 Error sending SITE command\n{1}".format(
+                            presp[0], presp))
+                elif self.__proxyType == E5FtpProxyType.Open:
+                    # send OPEN command
+                    presp = self.sendcmd("OPEN " + self.__host)
+                    if presp[0] != "2":
+                        raise E5FtpProxyError(
+                            "9{0}0 Error sending OPEN command\n{1}".format(
+                            presp[0], presp))
+        
+        # authenticate to the remote host or combined to proxy and remote host
+        resp = self.sendcmd("USER " + user)
+        if resp[0] == "3":
+            resp = self.sendcmd("PASS " + password)
+        if resp[0] == "3":
+            resp = self.sendcmd("ACCT " + acct)
+        if resp[0] != "2":
+            raise ftplib.error_reply(resp)
+        
+        if self.__proxyType == E5FtpProxyType.AuthResp:
+            # authorize to the FTP proxy
+            presp = self.sendcmd("AUTH " + self.__proxyUser)
+            if presp[0] == "3":
+                presp = self.sendcmd("RESP " + self.__proxyPassword)
+            if presp[0] != "2":
+                raise E5FtpProxyError("9{0}0 Error authorizing at proxy\n{1}".format(
+                    presp[0], presp))
+        
+        return resp

eric ide

mercurial