|
1 """An FTP client class and some helper functions. |
|
2 |
|
3 Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds |
|
4 |
|
5 Example: |
|
6 |
|
7 >>> from ftplib import FTP |
|
8 >>> ftp = FTP('ftp.python.org') # connect to host, default port |
|
9 >>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@ |
|
10 '230 Guest login ok, access restrictions apply.' |
|
11 >>> ftp.retrlines('LIST') # list directory contents |
|
12 total 9 |
|
13 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . |
|
14 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. |
|
15 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin |
|
16 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc |
|
17 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming |
|
18 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib |
|
19 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub |
|
20 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr |
|
21 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg |
|
22 '226 Transfer complete.' |
|
23 >>> ftp.quit() |
|
24 '221 Goodbye.' |
|
25 >>> |
|
26 |
|
27 A nice test that reveals some of the network dialogue would be: |
|
28 python ftplib.py -d localhost -l -p -l |
|
29 """ |
|
30 |
|
31 # |
|
32 # Changes and improvements suggested by Steve Majewski. |
|
33 # Modified by Jack to work on the mac. |
|
34 # Modified by Siebren to support docstrings and PASV. |
|
35 # |
|
36 |
|
37 import os |
|
38 import sys |
|
39 |
|
40 # Import SOCKS module if it exists, else standard socket module socket |
|
41 try: |
|
42 import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket |
|
43 from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn |
|
44 except ImportError: |
|
45 import socket |
|
46 |
|
47 __all__ = ["FTP","Netrc"] |
|
48 |
|
49 # Magic number from <socket.h> |
|
50 MSG_OOB = 0x1 # Process data out of band |
|
51 |
|
52 |
|
53 # The standard FTP server control port |
|
54 FTP_PORT = 21 |
|
55 |
|
56 |
|
57 # Exception raised when an error or invalid response is received |
|
58 class Error(Exception): pass |
|
59 class error_reply(Error): pass # unexpected [123]xx reply |
|
60 class error_temp(Error): pass # 4xx errors |
|
61 class error_perm(Error): pass # 5xx errors |
|
62 class error_proto(Error): pass # response does not begin with [1-5] |
|
63 |
|
64 |
|
65 # All exceptions (hopefully) that may be raised here and that aren't |
|
66 # (always) programming errors on our side |
|
67 all_errors = (Error, socket.error, IOError, EOFError) |
|
68 |
|
69 |
|
70 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) |
|
71 CRLF = '\r\n' |
|
72 |
|
73 |
|
74 # The class itself |
|
75 class FTP: |
|
76 |
|
77 '''An FTP client class. |
|
78 |
|
79 To create a connection, call the class using these argument: |
|
80 host, user, passwd, acct |
|
81 These are all strings, and have default value ''. |
|
82 Then use self.connect() with optional host and port argument. |
|
83 |
|
84 To download a file, use ftp.retrlines('RETR ' + filename), |
|
85 or ftp.retrbinary() with slightly different arguments. |
|
86 To upload a file, use ftp.storlines() or ftp.storbinary(), |
|
87 which have an open file as argument (see their definitions |
|
88 below for details). |
|
89 The download/upload functions first issue appropriate TYPE |
|
90 and PORT or PASV commands. |
|
91 ''' |
|
92 |
|
93 debugging = 0 |
|
94 host = '' |
|
95 port = FTP_PORT |
|
96 sock = None |
|
97 file = None |
|
98 welcome = None |
|
99 passiveserver = 1 |
|
100 |
|
101 # Initialization method (called by class instantiation). |
|
102 # Initialize host to localhost, port to standard ftp port |
|
103 # Optional arguments are host (for connect()), |
|
104 # and user, passwd, acct (for login()) |
|
105 def __init__(self, host='', user='', passwd='', acct=''): |
|
106 if host: |
|
107 self.connect(host) |
|
108 if user: self.login(user, passwd, acct) |
|
109 |
|
110 def connect(self, host = '', port = 0): |
|
111 '''Connect to host. Arguments are: |
|
112 - host: hostname to connect to (string, default previous host) |
|
113 - port: port to connect to (integer, default previous port)''' |
|
114 if host: self.host = host |
|
115 if port: self.port = port |
|
116 msg = "getaddrinfo returns an empty list" |
|
117 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM): |
|
118 af, socktype, proto, canonname, sa = res |
|
119 try: |
|
120 self.sock = socket.socket(af, socktype, proto) |
|
121 self.sock.connect(sa) |
|
122 except socket.error, msg: |
|
123 if self.sock: |
|
124 self.sock.close() |
|
125 self.sock = None |
|
126 continue |
|
127 break |
|
128 if not self.sock: |
|
129 raise socket.error, msg |
|
130 self.af = af |
|
131 self.file = self.sock.makefile('rb') |
|
132 self.welcome = self.getresp() |
|
133 return self.welcome |
|
134 |
|
135 def getwelcome(self): |
|
136 '''Get the welcome message from the server. |
|
137 (this is read and squirreled away by connect())''' |
|
138 if self.debugging: |
|
139 print '*welcome*', self.sanitize(self.welcome) |
|
140 return self.welcome |
|
141 |
|
142 def set_debuglevel(self, level): |
|
143 '''Set the debugging level. |
|
144 The required argument level means: |
|
145 0: no debugging output (default) |
|
146 1: print commands and responses but not body text etc. |
|
147 2: also print raw lines read and sent before stripping CR/LF''' |
|
148 self.debugging = level |
|
149 debug = set_debuglevel |
|
150 |
|
151 def set_pasv(self, val): |
|
152 '''Use passive or active mode for data transfers. |
|
153 With a false argument, use the normal PORT mode, |
|
154 With a true argument, use the PASV command.''' |
|
155 self.passiveserver = val |
|
156 |
|
157 # Internal: "sanitize" a string for printing |
|
158 def sanitize(self, s): |
|
159 if s[:5] == 'pass ' or s[:5] == 'PASS ': |
|
160 i = len(s) |
|
161 while i > 5 and s[i-1] in '\r\n': |
|
162 i = i-1 |
|
163 s = s[:5] + '*'*(i-5) + s[i:] |
|
164 return repr(s) |
|
165 |
|
166 # Internal: send one line to the server, appending CRLF |
|
167 def putline(self, line): |
|
168 line = line + CRLF |
|
169 if self.debugging > 1: print '*put*', self.sanitize(line) |
|
170 self.sock.sendall(line) |
|
171 |
|
172 # Internal: send one command to the server (through putline()) |
|
173 def putcmd(self, line): |
|
174 if self.debugging: print '*cmd*', self.sanitize(line) |
|
175 self.putline(line) |
|
176 |
|
177 # Internal: return one line from the server, stripping CRLF. |
|
178 # Raise EOFError if the connection is closed |
|
179 def getline(self): |
|
180 line = self.file.readline() |
|
181 if self.debugging > 1: |
|
182 print '*get*', self.sanitize(line) |
|
183 if not line: raise EOFError |
|
184 if line[-2:] == CRLF: line = line[:-2] |
|
185 elif line[-1:] in CRLF: line = line[:-1] |
|
186 return line |
|
187 |
|
188 # Internal: get a response from the server, which may possibly |
|
189 # consist of multiple lines. Return a single string with no |
|
190 # trailing CRLF. If the response consists of multiple lines, |
|
191 # these are separated by '\n' characters in the string |
|
192 def getmultiline(self): |
|
193 line = self.getline() |
|
194 if line[3:4] == '-': |
|
195 code = line[:3] |
|
196 while 1: |
|
197 nextline = self.getline() |
|
198 line = line + ('\n' + nextline) |
|
199 if nextline[:3] == code and \ |
|
200 nextline[3:4] != '-': |
|
201 break |
|
202 return line |
|
203 |
|
204 # Internal: get a response from the server. |
|
205 # Raise various errors if the response indicates an error |
|
206 def getresp(self): |
|
207 resp = self.getmultiline() |
|
208 if self.debugging: print '*resp*', self.sanitize(resp) |
|
209 self.lastresp = resp[:3] |
|
210 c = resp[:1] |
|
211 if c in ('1', '2', '3'): |
|
212 return resp |
|
213 if c == '4': |
|
214 raise error_temp, resp |
|
215 if c == '5': |
|
216 raise error_perm, resp |
|
217 raise error_proto, resp |
|
218 |
|
219 def voidresp(self): |
|
220 """Expect a response beginning with '2'.""" |
|
221 resp = self.getresp() |
|
222 if resp[0] != '2': |
|
223 raise error_reply, resp |
|
224 return resp |
|
225 |
|
226 def abort(self): |
|
227 '''Abort a file transfer. Uses out-of-band data. |
|
228 This does not follow the procedure from the RFC to send Telnet |
|
229 IP and Synch; that doesn't seem to work with the servers I've |
|
230 tried. Instead, just send the ABOR command as OOB data.''' |
|
231 line = 'ABOR' + CRLF |
|
232 if self.debugging > 1: print '*put urgent*', self.sanitize(line) |
|
233 self.sock.sendall(line, MSG_OOB) |
|
234 resp = self.getmultiline() |
|
235 if resp[:3] not in ('426', '226'): |
|
236 raise error_proto, resp |
|
237 |
|
238 def sendcmd(self, cmd): |
|
239 '''Send a command and return the response.''' |
|
240 self.putcmd(cmd) |
|
241 return self.getresp() |
|
242 |
|
243 def voidcmd(self, cmd): |
|
244 """Send a command and expect a response beginning with '2'.""" |
|
245 self.putcmd(cmd) |
|
246 return self.voidresp() |
|
247 |
|
248 def sendport(self, host, port): |
|
249 '''Send a PORT command with the current host and the given |
|
250 port number. |
|
251 ''' |
|
252 hbytes = host.split('.') |
|
253 pbytes = [repr(port/256), repr(port%256)] |
|
254 bytes = hbytes + pbytes |
|
255 cmd = 'PORT ' + ','.join(bytes) |
|
256 return self.voidcmd(cmd) |
|
257 |
|
258 def sendeprt(self, host, port): |
|
259 '''Send a EPRT command with the current host and the given port number.''' |
|
260 af = 0 |
|
261 if self.af == socket.AF_INET: |
|
262 af = 1 |
|
263 if self.af == socket.AF_INET6: |
|
264 af = 2 |
|
265 if af == 0: |
|
266 raise error_proto, 'unsupported address family' |
|
267 fields = ['', repr(af), host, repr(port), ''] |
|
268 cmd = 'EPRT ' + '|'.join(fields) |
|
269 return self.voidcmd(cmd) |
|
270 |
|
271 def makeport(self): |
|
272 '''Create a new socket and send a PORT command for it.''' |
|
273 msg = "getaddrinfo returns an empty list" |
|
274 sock = None |
|
275 for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE): |
|
276 af, socktype, proto, canonname, sa = res |
|
277 try: |
|
278 sock = socket.socket(af, socktype, proto) |
|
279 sock.bind(sa) |
|
280 except socket.error, msg: |
|
281 if sock: |
|
282 sock.close() |
|
283 sock = None |
|
284 continue |
|
285 break |
|
286 if not sock: |
|
287 raise socket.error, msg |
|
288 sock.listen(1) |
|
289 port = sock.getsockname()[1] # Get proper port |
|
290 host = self.sock.getsockname()[0] # Get proper host |
|
291 if self.af == socket.AF_INET: |
|
292 resp = self.sendport(host, port) |
|
293 else: |
|
294 resp = self.sendeprt(host, port) |
|
295 return sock |
|
296 |
|
297 def makepasv(self): |
|
298 if self.af == socket.AF_INET: |
|
299 host, port = parse227(self.sendcmd('PASV')) |
|
300 else: |
|
301 host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) |
|
302 return host, port |
|
303 |
|
304 def ntransfercmd(self, cmd, rest=None): |
|
305 """Initiate a transfer over the data connection. |
|
306 |
|
307 If the transfer is active, send a port command and the |
|
308 transfer command, and accept the connection. If the server is |
|
309 passive, send a pasv command, connect to it, and start the |
|
310 transfer command. Either way, return the socket for the |
|
311 connection and the expected size of the transfer. The |
|
312 expected size may be None if it could not be determined. |
|
313 |
|
314 Optional `rest' argument can be a string that is sent as the |
|
315 argument to a RESTART command. This is essentially a server |
|
316 marker used to tell the server to skip over any data up to the |
|
317 given marker. |
|
318 """ |
|
319 size = None |
|
320 if self.passiveserver: |
|
321 host, port = self.makepasv() |
|
322 af, socktype, proto, canon, sa = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[0] |
|
323 conn = socket.socket(af, socktype, proto) |
|
324 conn.connect(sa) |
|
325 if rest is not None: |
|
326 self.sendcmd("REST %s" % rest) |
|
327 resp = self.sendcmd(cmd) |
|
328 # Some servers apparently send a 200 reply to |
|
329 # a LIST or STOR command, before the 150 reply |
|
330 # (and way before the 226 reply). This seems to |
|
331 # be in violation of the protocol (which only allows |
|
332 # 1xx or error messages for LIST), so we just discard |
|
333 # this response. |
|
334 if resp[0] == '2': |
|
335 resp = self.getresp() |
|
336 if resp[0] != '1': |
|
337 raise error_reply, resp |
|
338 else: |
|
339 sock = self.makeport() |
|
340 if rest is not None: |
|
341 self.sendcmd("REST %s" % rest) |
|
342 resp = self.sendcmd(cmd) |
|
343 # See above. |
|
344 if resp[0] == '2': |
|
345 resp = self.getresp() |
|
346 if resp[0] != '1': |
|
347 raise error_reply, resp |
|
348 conn, sockaddr = sock.accept() |
|
349 if resp[:3] == '150': |
|
350 # this is conditional in case we received a 125 |
|
351 size = parse150(resp) |
|
352 return conn, size |
|
353 |
|
354 def transfercmd(self, cmd, rest=None): |
|
355 """Like ntransfercmd() but returns only the socket.""" |
|
356 return self.ntransfercmd(cmd, rest)[0] |
|
357 |
|
358 def login(self, user = '', passwd = '', acct = ''): |
|
359 '''Login, default anonymous.''' |
|
360 if not user: user = 'anonymous' |
|
361 if not passwd: passwd = '' |
|
362 if not acct: acct = '' |
|
363 if user == 'anonymous' and passwd in ('', '-'): |
|
364 # If there is no anonymous ftp password specified |
|
365 # then we'll just use anonymous@ |
|
366 # We don't send any other thing because: |
|
367 # - We want to remain anonymous |
|
368 # - We want to stop SPAM |
|
369 # - We don't want to let ftp sites to discriminate by the user, |
|
370 # host or country. |
|
371 passwd = passwd + 'anonymous@' |
|
372 resp = self.sendcmd('USER ' + user) |
|
373 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd) |
|
374 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct) |
|
375 if resp[0] != '2': |
|
376 raise error_reply, resp |
|
377 return resp |
|
378 |
|
379 def retrbinary(self, cmd, callback, blocksize=8192, rest=None): |
|
380 """Retrieve data in binary mode. |
|
381 |
|
382 `cmd' is a RETR command. `callback' is a callback function is |
|
383 called for each block. No more than `blocksize' number of |
|
384 bytes will be read from the socket. Optional `rest' is passed |
|
385 to transfercmd(). |
|
386 |
|
387 A new port is created for you. Return the response code. |
|
388 """ |
|
389 self.voidcmd('TYPE I') |
|
390 conn = self.transfercmd(cmd, rest) |
|
391 while 1: |
|
392 data = conn.recv(blocksize) |
|
393 if not data: |
|
394 break |
|
395 callback(data) |
|
396 conn.close() |
|
397 return self.voidresp() |
|
398 |
|
399 def retrlines(self, cmd, callback = None): |
|
400 '''Retrieve data in line mode. |
|
401 The argument is a RETR or LIST command. |
|
402 The callback function (2nd argument) is called for each line, |
|
403 with trailing CRLF stripped. This creates a new port for you. |
|
404 print_line() is the default callback.''' |
|
405 if callback is None: callback = print_line |
|
406 resp = self.sendcmd('TYPE A') |
|
407 conn = self.transfercmd(cmd) |
|
408 fp = conn.makefile('rb') |
|
409 while 1: |
|
410 line = fp.readline() |
|
411 if self.debugging > 2: print '*retr*', repr(line) |
|
412 if not line: |
|
413 break |
|
414 if line[-2:] == CRLF: |
|
415 line = line[:-2] |
|
416 elif line[-1:] == '\n': |
|
417 line = line[:-1] |
|
418 callback(line) |
|
419 fp.close() |
|
420 conn.close() |
|
421 return self.voidresp() |
|
422 |
|
423 def storbinary(self, cmd, fp, blocksize=8192): |
|
424 '''Store a file in binary mode.''' |
|
425 self.voidcmd('TYPE I') |
|
426 conn = self.transfercmd(cmd) |
|
427 while 1: |
|
428 buf = fp.read(blocksize) |
|
429 if not buf: break |
|
430 conn.sendall(buf) |
|
431 conn.close() |
|
432 return self.voidresp() |
|
433 |
|
434 def storlines(self, cmd, fp): |
|
435 '''Store a file in line mode.''' |
|
436 self.voidcmd('TYPE A') |
|
437 conn = self.transfercmd(cmd) |
|
438 while 1: |
|
439 buf = fp.readline() |
|
440 if not buf: break |
|
441 if buf[-2:] != CRLF: |
|
442 if buf[-1] in CRLF: buf = buf[:-1] |
|
443 buf = buf + CRLF |
|
444 conn.sendall(buf) |
|
445 conn.close() |
|
446 return self.voidresp() |
|
447 |
|
448 def acct(self, password): |
|
449 '''Send new account name.''' |
|
450 cmd = 'ACCT ' + password |
|
451 return self.voidcmd(cmd) |
|
452 |
|
453 def nlst(self, *args): |
|
454 '''Return a list of files in a given directory (default the current).''' |
|
455 cmd = 'NLST' |
|
456 for arg in args: |
|
457 cmd = cmd + (' ' + arg) |
|
458 files = [] |
|
459 self.retrlines(cmd, files.append) |
|
460 return files |
|
461 |
|
462 def dir(self, *args): |
|
463 '''List a directory in long form. |
|
464 By default list current directory to stdout. |
|
465 Optional last argument is callback function; all |
|
466 non-empty arguments before it are concatenated to the |
|
467 LIST command. (This *should* only be used for a pathname.)''' |
|
468 cmd = 'LIST' |
|
469 func = None |
|
470 if args[-1:] and type(args[-1]) != type(''): |
|
471 args, func = args[:-1], args[-1] |
|
472 for arg in args: |
|
473 if arg: |
|
474 cmd = cmd + (' ' + arg) |
|
475 self.retrlines(cmd, func) |
|
476 |
|
477 def rename(self, fromname, toname): |
|
478 '''Rename a file.''' |
|
479 resp = self.sendcmd('RNFR ' + fromname) |
|
480 if resp[0] != '3': |
|
481 raise error_reply, resp |
|
482 return self.voidcmd('RNTO ' + toname) |
|
483 |
|
484 def delete(self, filename): |
|
485 '''Delete a file.''' |
|
486 resp = self.sendcmd('DELE ' + filename) |
|
487 if resp[:3] in ('250', '200'): |
|
488 return resp |
|
489 elif resp[:1] == '5': |
|
490 raise error_perm, resp |
|
491 else: |
|
492 raise error_reply, resp |
|
493 |
|
494 def cwd(self, dirname): |
|
495 '''Change to a directory.''' |
|
496 if dirname == '..': |
|
497 try: |
|
498 return self.voidcmd('CDUP') |
|
499 except error_perm, msg: |
|
500 if msg.args[0][:3] != '500': |
|
501 raise |
|
502 elif dirname == '': |
|
503 dirname = '.' # does nothing, but could return error |
|
504 cmd = 'CWD ' + dirname |
|
505 return self.voidcmd(cmd) |
|
506 |
|
507 def size(self, filename): |
|
508 '''Retrieve the size of a file.''' |
|
509 # Note that the RFC doesn't say anything about 'SIZE' |
|
510 resp = self.sendcmd('SIZE ' + filename) |
|
511 if resp[:3] == '213': |
|
512 s = resp[3:].strip() |
|
513 try: |
|
514 return int(s) |
|
515 except (OverflowError, ValueError): |
|
516 return long(s) |
|
517 |
|
518 def mkd(self, dirname): |
|
519 '''Make a directory, return its full pathname.''' |
|
520 resp = self.sendcmd('MKD ' + dirname) |
|
521 return parse257(resp) |
|
522 |
|
523 def rmd(self, dirname): |
|
524 '''Remove a directory.''' |
|
525 return self.voidcmd('RMD ' + dirname) |
|
526 |
|
527 def pwd(self): |
|
528 '''Return current working directory.''' |
|
529 resp = self.sendcmd('PWD') |
|
530 return parse257(resp) |
|
531 |
|
532 def quit(self): |
|
533 '''Quit, and close the connection.''' |
|
534 resp = self.voidcmd('QUIT') |
|
535 self.close() |
|
536 return resp |
|
537 |
|
538 def close(self): |
|
539 '''Close the connection without assuming anything about it.''' |
|
540 if self.file: |
|
541 self.file.close() |
|
542 self.sock.close() |
|
543 self.file = self.sock = None |
|
544 |
|
545 |
|
546 _150_re = None |
|
547 |
|
548 def parse150(resp): |
|
549 '''Parse the '150' response for a RETR request. |
|
550 Returns the expected transfer size or None; size is not guaranteed to |
|
551 be present in the 150 message. |
|
552 ''' |
|
553 if resp[:3] != '150': |
|
554 raise error_reply, resp |
|
555 global _150_re |
|
556 if _150_re is None: |
|
557 import re |
|
558 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE) |
|
559 m = _150_re.match(resp) |
|
560 if not m: |
|
561 return None |
|
562 s = m.group(1) |
|
563 try: |
|
564 return int(s) |
|
565 except (OverflowError, ValueError): |
|
566 return long(s) |
|
567 |
|
568 |
|
569 _227_re = None |
|
570 |
|
571 def parse227(resp): |
|
572 '''Parse the '227' response for a PASV request. |
|
573 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' |
|
574 Return ('host.addr.as.numbers', port#) tuple.''' |
|
575 |
|
576 if resp[:3] != '227': |
|
577 raise error_reply, resp |
|
578 global _227_re |
|
579 if _227_re is None: |
|
580 import re |
|
581 _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)') |
|
582 m = _227_re.search(resp) |
|
583 if not m: |
|
584 raise error_proto, resp |
|
585 numbers = m.groups() |
|
586 host = '.'.join(numbers[:4]) |
|
587 port = (int(numbers[4]) << 8) + int(numbers[5]) |
|
588 return host, port |
|
589 |
|
590 |
|
591 def parse229(resp, peer): |
|
592 '''Parse the '229' response for a EPSV request. |
|
593 Raises error_proto if it does not contain '(|||port|)' |
|
594 Return ('host.addr.as.numbers', port#) tuple.''' |
|
595 |
|
596 if resp[:3] != '229': |
|
597 raise error_reply, resp |
|
598 left = resp.find('(') |
|
599 if left < 0: raise error_proto, resp |
|
600 right = resp.find(')', left + 1) |
|
601 if right < 0: |
|
602 raise error_proto, resp # should contain '(|||port|)' |
|
603 if resp[left + 1] != resp[right - 1]: |
|
604 raise error_proto, resp |
|
605 parts = resp[left + 1:right].split(resp[left+1]) |
|
606 if len(parts) != 5: |
|
607 raise error_proto, resp |
|
608 host = peer[0] |
|
609 port = int(parts[3]) |
|
610 return host, port |
|
611 |
|
612 |
|
613 def parse257(resp): |
|
614 '''Parse the '257' response for a MKD or PWD request. |
|
615 This is a response to a MKD or PWD request: a directory name. |
|
616 Returns the directoryname in the 257 reply.''' |
|
617 |
|
618 if resp[:3] != '257': |
|
619 raise error_reply, resp |
|
620 if resp[3:5] != ' "': |
|
621 return '' # Not compliant to RFC 959, but UNIX ftpd does this |
|
622 dirname = '' |
|
623 i = 5 |
|
624 n = len(resp) |
|
625 while i < n: |
|
626 c = resp[i] |
|
627 i = i+1 |
|
628 if c == '"': |
|
629 if i >= n or resp[i] != '"': |
|
630 break |
|
631 i = i+1 |
|
632 dirname = dirname + c |
|
633 return dirname |
|
634 |
|
635 |
|
636 def print_line(line): |
|
637 '''Default retrlines callback to print a line.''' |
|
638 print line |
|
639 |
|
640 |
|
641 def ftpcp(source, sourcename, target, targetname = '', type = 'I'): |
|
642 '''Copy file from one FTP-instance to another.''' |
|
643 if not targetname: targetname = sourcename |
|
644 type = 'TYPE ' + type |
|
645 source.voidcmd(type) |
|
646 target.voidcmd(type) |
|
647 sourcehost, sourceport = parse227(source.sendcmd('PASV')) |
|
648 target.sendport(sourcehost, sourceport) |
|
649 # RFC 959: the user must "listen" [...] BEFORE sending the |
|
650 # transfer request. |
|
651 # So: STOR before RETR, because here the target is a "user". |
|
652 treply = target.sendcmd('STOR ' + targetname) |
|
653 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959 |
|
654 sreply = source.sendcmd('RETR ' + sourcename) |
|
655 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959 |
|
656 source.voidresp() |
|
657 target.voidresp() |
|
658 |
|
659 |
|
660 class Netrc: |
|
661 """Class to parse & provide access to 'netrc' format files. |
|
662 |
|
663 See the netrc(4) man page for information on the file format. |
|
664 |
|
665 WARNING: This class is obsolete -- use module netrc instead. |
|
666 |
|
667 """ |
|
668 __defuser = None |
|
669 __defpasswd = None |
|
670 __defacct = None |
|
671 |
|
672 def __init__(self, filename=None): |
|
673 if filename is None: |
|
674 if "HOME" in os.environ: |
|
675 filename = os.path.join(os.environ["HOME"], |
|
676 ".netrc") |
|
677 else: |
|
678 raise IOError, \ |
|
679 "specify file to load or set $HOME" |
|
680 self.__hosts = {} |
|
681 self.__macros = {} |
|
682 fp = open(filename, "r") |
|
683 in_macro = 0 |
|
684 while 1: |
|
685 line = fp.readline() |
|
686 if not line: break |
|
687 if in_macro and line.strip(): |
|
688 macro_lines.append(line) |
|
689 continue |
|
690 elif in_macro: |
|
691 self.__macros[macro_name] = tuple(macro_lines) |
|
692 in_macro = 0 |
|
693 words = line.split() |
|
694 host = user = passwd = acct = None |
|
695 default = 0 |
|
696 i = 0 |
|
697 while i < len(words): |
|
698 w1 = words[i] |
|
699 if i+1 < len(words): |
|
700 w2 = words[i + 1] |
|
701 else: |
|
702 w2 = None |
|
703 if w1 == 'default': |
|
704 default = 1 |
|
705 elif w1 == 'machine' and w2: |
|
706 host = w2.lower() |
|
707 i = i + 1 |
|
708 elif w1 == 'login' and w2: |
|
709 user = w2 |
|
710 i = i + 1 |
|
711 elif w1 == 'password' and w2: |
|
712 passwd = w2 |
|
713 i = i + 1 |
|
714 elif w1 == 'account' and w2: |
|
715 acct = w2 |
|
716 i = i + 1 |
|
717 elif w1 == 'macdef' and w2: |
|
718 macro_name = w2 |
|
719 macro_lines = [] |
|
720 in_macro = 1 |
|
721 break |
|
722 i = i + 1 |
|
723 if default: |
|
724 self.__defuser = user or self.__defuser |
|
725 self.__defpasswd = passwd or self.__defpasswd |
|
726 self.__defacct = acct or self.__defacct |
|
727 if host: |
|
728 if host in self.__hosts: |
|
729 ouser, opasswd, oacct = \ |
|
730 self.__hosts[host] |
|
731 user = user or ouser |
|
732 passwd = passwd or opasswd |
|
733 acct = acct or oacct |
|
734 self.__hosts[host] = user, passwd, acct |
|
735 fp.close() |
|
736 |
|
737 def get_hosts(self): |
|
738 """Return a list of hosts mentioned in the .netrc file.""" |
|
739 return self.__hosts.keys() |
|
740 |
|
741 def get_account(self, host): |
|
742 """Returns login information for the named host. |
|
743 |
|
744 The return value is a triple containing userid, |
|
745 password, and the accounting field. |
|
746 |
|
747 """ |
|
748 host = host.lower() |
|
749 user = passwd = acct = None |
|
750 if host in self.__hosts: |
|
751 user, passwd, acct = self.__hosts[host] |
|
752 user = user or self.__defuser |
|
753 passwd = passwd or self.__defpasswd |
|
754 acct = acct or self.__defacct |
|
755 return user, passwd, acct |
|
756 |
|
757 def get_macros(self): |
|
758 """Return a list of all defined macro names.""" |
|
759 return self.__macros.keys() |
|
760 |
|
761 def get_macro(self, macro): |
|
762 """Return a sequence of lines which define a named macro.""" |
|
763 return self.__macros[macro] |
|
764 |
|
765 |
|
766 |
|
767 def test(): |
|
768 '''Test program. |
|
769 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... |
|
770 |
|
771 -d dir |
|
772 -l list |
|
773 -p password |
|
774 ''' |
|
775 |
|
776 if len(sys.argv) < 2: |
|
777 print test.__doc__ |
|
778 sys.exit(0) |
|
779 |
|
780 debugging = 0 |
|
781 rcfile = None |
|
782 while sys.argv[1] == '-d': |
|
783 debugging = debugging+1 |
|
784 del sys.argv[1] |
|
785 if sys.argv[1][:2] == '-r': |
|
786 # get name of alternate ~/.netrc file: |
|
787 rcfile = sys.argv[1][2:] |
|
788 del sys.argv[1] |
|
789 host = sys.argv[1] |
|
790 ftp = FTP(host) |
|
791 ftp.set_debuglevel(debugging) |
|
792 userid = passwd = acct = '' |
|
793 try: |
|
794 netrc = Netrc(rcfile) |
|
795 except IOError: |
|
796 if rcfile is not None: |
|
797 sys.stderr.write("Could not open account file" |
|
798 " -- using anonymous login.") |
|
799 else: |
|
800 try: |
|
801 userid, passwd, acct = netrc.get_account(host) |
|
802 except KeyError: |
|
803 # no account for host |
|
804 sys.stderr.write( |
|
805 "No account -- using anonymous login.") |
|
806 ftp.login(userid, passwd, acct) |
|
807 for file in sys.argv[2:]: |
|
808 if file[:2] == '-l': |
|
809 ftp.dir(file[2:]) |
|
810 elif file[:2] == '-d': |
|
811 cmd = 'CWD' |
|
812 if file[2:]: cmd = cmd + ' ' + file[2:] |
|
813 resp = ftp.sendcmd(cmd) |
|
814 elif file == '-p': |
|
815 ftp.set_pasv(not ftp.passiveserver) |
|
816 else: |
|
817 ftp.retrbinary('RETR ' + file, \ |
|
818 sys.stdout.write, 1024) |
|
819 ftp.quit() |
|
820 |
|
821 |
|
822 if __name__ == '__main__': |
|
823 test() |