| 44 | | |
|---|
| 45 | | |
|---|
| 46 | | class MockSession: |
|---|
| 47 | | """ |
|---|
| 48 | | Mock implementation of ftplib.FTP . For information on mock |
|---|
| 49 | | objects see http://www.mockobjects.com/ . |
|---|
| 50 | | """ |
|---|
| 51 | | |
|---|
| 52 | | # taken from ftplib.FTP |
|---|
| 53 | | host = '' |
|---|
| 54 | | sock = None |
|---|
| 55 | | file = None |
|---|
| 56 | | welcome = None |
|---|
| 57 | | passiveserver = 1 |
|---|
| 58 | | |
|---|
| 59 | | # mock object settings |
|---|
| 60 | | voidresp_raise = None |
|---|
| 61 | | voidresp_result = None |
|---|
| 62 | | sendcmd_result = None |
|---|
| 63 | | voidcmd_exception = None |
|---|
| 64 | | voidcmd_result = None |
|---|
| 65 | | mock_socket_file_contents = '' |
|---|
| 66 | | login_raise = None |
|---|
| 67 | | login_result = None |
|---|
| 68 | | |
|---|
| 69 | | # Initialization method (called by class instantiation). |
|---|
| 70 | | # Initialize host to localhost, port to standard ftp port |
|---|
| 71 | | # Optional arguments are host (for connect()), |
|---|
| 72 | | # and user, passwd, acct (for login()) |
|---|
| 73 | | def __init__(self, host='', user='', passwd='', acct=''): |
|---|
| 74 | | if host: |
|---|
| 75 | | self.connect(host) |
|---|
| 76 | | if user: self.login(user, passwd, acct) |
|---|
| 77 | | |
|---|
| 78 | | def connect(self, host = '', port = 0): |
|---|
| 79 | | """Connect to host. Arguments are: |
|---|
| 80 | | - host: hostname to connect to (string, default previous host) |
|---|
| 81 | | - port: port to connect to (integer, default previous port)""" |
|---|
| 82 | | if host: self.host = host |
|---|
| 83 | | if port: self.port = port |
|---|
| 84 | | self.passiveserver = 1 |
|---|
| 85 | | self.file = StringIO.StringIO() |
|---|
| 86 | | self.welcome = 'Welcome to the MockSession class! ;-)' |
|---|
| 87 | | return self.welcome |
|---|
| 88 | | |
|---|
| 89 | | def getwelcome(self): |
|---|
| 90 | | """Get the welcome message from the server. |
|---|
| 91 | | (this is read and squirreled away by connect())""" |
|---|
| 92 | | return self.welcome |
|---|
| 93 | | |
|---|
| 94 | | def set_pasv(self, val): |
|---|
| 95 | | """Use passive or active mode for data transfers. |
|---|
| 96 | | With a false argument, use the normal PORT mode, |
|---|
| 97 | | With a true argument, use the PASV command.""" |
|---|
| 98 | | self.passiveserver = val |
|---|
| 99 | | |
|---|
| 100 | | def voidresp(self): |
|---|
| 101 | | """Expect a response beginning with '2'.""" |
|---|
| 102 | | if self.voidresp_raise: |
|---|
| 103 | | raise ftplib.error_reply, resp |
|---|
| 104 | | return self.voidresp_result |
|---|
| 105 | | |
|---|
| 106 | | def sendcmd(self, cmd): |
|---|
| 107 | | """Send a command and return the response.""" |
|---|
| 108 | | return self.sendcmd_result |
|---|
| 109 | | |
|---|
| 110 | | def voidcmd(self, cmd): |
|---|
| 111 | | """Send a command and expect a response beginning with '2'.""" |
|---|
| 112 | | if self.voidcmd_exception is not None: |
|---|
| 113 | | raise self.voidcmd_exception |
|---|
| 114 | | return self.voidcmd_result |
|---|
| 115 | | |
|---|
| 116 | | def ntransfercmd(self, cmd, rest=None): |
|---|
| 117 | | """Initiate a transfer over the data connection. |
|---|
| 118 | | |
|---|
| 119 | | If the transfer is active, send a port command and the |
|---|
| 120 | | transfer command, and accept the connection. If the server is |
|---|
| 121 | | passive, send a pasv command, connect to it, and start the |
|---|
| 122 | | transfer command. Either way, return the socket for the |
|---|
| 123 | | connection and the expected size of the transfer. The |
|---|
| 124 | | expected size may be None if it could not be determined. |
|---|
| 125 | | |
|---|
| 126 | | Optional `rest' argument can be a string that is sent as the |
|---|
| 127 | | argument to a RESTART command. This is essentially a server |
|---|
| 128 | | marker used to tell the server to skip over any data up to the |
|---|
| 129 | | given marker. |
|---|
| 130 | | """ |
|---|
| 131 | | conn = MockSocket(self.mock_socket_file_contents) |
|---|
| 132 | | size = self.ntransfercmd_size |
|---|
| 133 | | return conn, size |
|---|
| 134 | | |
|---|
| 135 | | def transfercmd(self, cmd, rest=None): |
|---|
| 136 | | """Like ntransfercmd() but returns only the socket.""" |
|---|
| 137 | | return self.ntransfercmd(cmd, rest)[0] |
|---|
| 138 | | |
|---|
| 139 | | def login(self, user='', passwd='', acct=''): |
|---|
| 140 | | """Login, default anonymous.""" |
|---|
| 141 | | if self.login_raise: |
|---|
| 142 | | raise ftplib.error_reply, self.login_result |
|---|
| 143 | | return self.login_result |
|---|
| 144 | | |
|---|
| 145 | | def retrbinary(self, cmd, callback, blocksize=8192, rest=None): |
|---|
| 146 | | """Retrieve data in binary mode. |
|---|
| 147 | | |
|---|
| 148 | | `cmd' is a RETR command. `callback' is a callback function is |
|---|
| 149 | | called for each block. No more than `blocksize' number of |
|---|
| 150 | | bytes will be read from the socket. Optional `rest' is passed |
|---|
| 151 | | to transfercmd(). |
|---|
| 152 | | |
|---|
| 153 | | A new port is created for you. Return the response code. |
|---|
| 154 | | """ |
|---|
| 155 | | self.voidcmd('TYPE I') |
|---|
| 156 | | conn = self.transfercmd(cmd, rest) |
|---|
| 157 | | while 1: |
|---|
| 158 | | data = conn.recv(blocksize) |
|---|
| 159 | | if not data: |
|---|
| 160 | | break |
|---|
| 161 | | callback(data) |
|---|
| 162 | | conn.close() |
|---|
| 163 | | return self.voidresp() |
|---|
| 164 | | |
|---|
| 165 | | def retrlines(self, cmd, callback = None): |
|---|
| 166 | | """Retrieve data in line mode. |
|---|
| 167 | | The argument is a RETR or LIST command. |
|---|
| 168 | | The callback function (2nd argument) is called for each line, |
|---|
| 169 | | with trailing CRLF stripped. This creates a new port for you. |
|---|
| 170 | | print_line() is the default callback.""" |
|---|
| 171 | | if not callback: callback = print_line |
|---|
| 172 | | resp = self.sendcmd('TYPE A') |
|---|
| 173 | | conn = self.transfercmd(cmd) |
|---|
| 174 | | fp = conn.makefile('rb') |
|---|
| 175 | | while 1: |
|---|
| 176 | | line = fp.readline() |
|---|
| 177 | | if self.debugging > 2: print '*retr*', `line` |
|---|
| 178 | | if not line: |
|---|
| 179 | | break |
|---|
| 180 | | if line[-2:] == CRLF: |
|---|
| 181 | | line = line[:-2] |
|---|
| 182 | | elif line[-1:] == '\n': |
|---|
| 183 | | line = line[:-1] |
|---|
| 184 | | callback(line) |
|---|
| 185 | | fp.close() |
|---|
| 186 | | conn.close() |
|---|
| 187 | | return self.voidresp() |
|---|
| 188 | | |
|---|
| 189 | | def storbinary(self, cmd, fp, blocksize=8192): |
|---|
| 190 | | """Store a file in binary mode.""" |
|---|
| 191 | | self.voidcmd('TYPE I') |
|---|
| 192 | | conn = self.transfercmd(cmd) |
|---|
| 193 | | while 1: |
|---|
| 194 | | buf = fp.read(blocksize) |
|---|
| 195 | | if not buf: break |
|---|
| 196 | | conn.send(buf) |
|---|
| 197 | | conn.close() |
|---|
| 198 | | return self.voidresp() |
|---|
| 199 | | |
|---|
| 200 | | def storlines(self, cmd, fp): |
|---|
| 201 | | """Store a file in line mode.""" |
|---|
| 202 | | self.voidcmd('TYPE A') |
|---|
| 203 | | conn = self.transfercmd(cmd) |
|---|
| 204 | | while 1: |
|---|
| 205 | | buf = fp.readline() |
|---|
| 206 | | if not buf: break |
|---|
| 207 | | if buf[-2:] != CRLF: |
|---|
| 208 | | if buf[-1] in CRLF: buf = buf[:-1] |
|---|
| 209 | | buf = buf + CRLF |
|---|
| 210 | | conn.send(buf) |
|---|
| 211 | | conn.close() |
|---|
| 212 | | return self.voidresp() |
|---|
| 213 | | |
|---|
| 214 | | def acct(self, password): |
|---|
| 215 | | """Send new account name.""" |
|---|
| 216 | | cmd = 'ACCT ' + password |
|---|
| 217 | | return self.voidcmd(cmd) |
|---|
| 218 | | |
|---|
| 219 | | def nlst(self, *args): |
|---|
| 220 | | """Return a list of files in a given directory (default the current).""" |
|---|
| 221 | | cmd = 'NLST' |
|---|
| 222 | | for arg in args: |
|---|
| 223 | | cmd = cmd + (' ' + arg) |
|---|
| 224 | | files = [] |
|---|
| 225 | | self.retrlines(cmd, files.append) |
|---|
| 226 | | return files |
|---|
| 227 | | |
|---|
| 228 | | def dir(self, *args): |
|---|
| 229 | | """List a directory in long form. |
|---|
| 230 | | By default list current directory to stdout. |
|---|
| 231 | | Optional last argument is callback function; all |
|---|
| 232 | | non-empty arguments before it are concatenated to the |
|---|
| 233 | | LIST command. (This *should* only be used for a pathname.)""" |
|---|
| 234 | | cmd = 'LIST' |
|---|
| 235 | | func = None |
|---|
| 236 | | if args[-1:] and type(args[-1]) != type(''): |
|---|
| 237 | | args, func = args[:-1], args[-1] |
|---|
| 238 | | for arg in args: |
|---|
| 239 | | if arg: |
|---|
| 240 | | cmd = cmd + (' ' + arg) |
|---|
| 241 | | self.retrlines(cmd, func) |
|---|
| 242 | | |
|---|
| 243 | | def rename(self, fromname, toname): |
|---|
| 244 | | """Rename a file.""" |
|---|
| 245 | | resp = self.sendcmd('RNFR ' + fromname) |
|---|
| 246 | | if resp[0] != '3': |
|---|
| 247 | | raise error_reply, resp |
|---|
| 248 | | return self.voidcmd('RNTO ' + toname) |
|---|
| 249 | | |
|---|
| 250 | | def delete(self, filename): |
|---|
| 251 | | """Delete a file.""" |
|---|
| 252 | | resp = self.sendcmd('DELE ' + filename) |
|---|
| 253 | | if resp[:3] in ('250', '200'): |
|---|
| 254 | | return resp |
|---|
| 255 | | elif resp[:1] == '5': |
|---|
| 256 | | raise error_perm, resp |
|---|
| 257 | | else: |
|---|
| 258 | | raise error_reply, resp |
|---|
| 259 | | |
|---|
| 260 | | def cwd(self, dirname): |
|---|
| 261 | | """Change to a directory.""" |
|---|
| 262 | | if dirname == '..': |
|---|
| 263 | | try: |
|---|
| 264 | | return self.voidcmd('CDUP') |
|---|
| 265 | | except error_perm, msg: |
|---|
| 266 | | if msg[:3] != '500': |
|---|
| 267 | | raise error_perm, msg |
|---|
| 268 | | elif dirname == '': |
|---|
| 269 | | dirname = '.' # does nothing, but could return error |
|---|
| 270 | | cmd = 'CWD ' + dirname |
|---|
| 271 | | return self.voidcmd(cmd) |
|---|
| 272 | | |
|---|
| 273 | | def size(self, filename): |
|---|
| 274 | | """Retrieve the size of a file.""" |
|---|
| 275 | | # Note that the RFC doesn't say anything about 'SIZE' |
|---|
| 276 | | resp = self.sendcmd('SIZE ' + filename) |
|---|
| 277 | | if resp[:3] == '213': |
|---|
| 278 | | s = resp[3:].strip() |
|---|
| 279 | | try: |
|---|
| 280 | | return int(s) |
|---|
| 281 | | except (OverflowError, ValueError): |
|---|
| 282 | | return long(s) |
|---|
| 283 | | |
|---|
| 284 | | def mkd(self, dirname): |
|---|
| 285 | | """Make a directory, return its full pathname.""" |
|---|
| 286 | | resp = self.sendcmd('MKD ' + dirname) |
|---|
| 287 | | return parse257(resp) |
|---|
| 288 | | |
|---|
| 289 | | def rmd(self, dirname): |
|---|
| 290 | | """Remove a directory.""" |
|---|
| 291 | | return self.voidcmd('RMD ' + dirname) |
|---|
| 292 | | |
|---|
| 293 | | def pwd(self): |
|---|
| 294 | | """Return current working directory.""" |
|---|
| 295 | | resp = self.sendcmd('PWD') |
|---|
| 296 | | return parse257(resp) |
|---|
| 297 | | |
|---|
| 298 | | def quit(self): |
|---|
| 299 | | """Quit, and close the connection.""" |
|---|
| 300 | | resp = self.voidcmd('QUIT') |
|---|
| 301 | | self.close() |
|---|
| 302 | | return resp |
|---|
| 303 | | |
|---|
| 304 | | def close(self): |
|---|
| 305 | | """Close the connection without assuming anything about it.""" |
|---|
| 306 | | if self.file: |
|---|
| 307 | | self.file.close() |
|---|
| 308 | | self.sock.close() |
|---|
| 309 | | self.file = self.sock = None |
|---|
| 310 | | |
|---|