1 """An FTP client class and some helper functions.
3 Based on RFC 959: File Transfer Protocol
4 (FTP), by J. Postel and J. Reynolds
8 >>> from ftplib import FTP
9 >>> ftp = FTP('ftp.python.org') # connect to host, default port
10 >>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
11 '230 Guest login ok, access restrictions apply.'
12 >>> ftp.retrlines('LIST') # list directory contents
14 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
15 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
16 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
17 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
18 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
19 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
20 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
21 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
22 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
23 '226 Transfer complete.'
28 A nice test that reveals some of the network dialogue would be:
29 python ftplib.py -d localhost -l -p -l
33 # Changes and improvements suggested by Steve Majewski.
34 # Modified by Jack to work on the mac.
35 # Modified by Siebren to support docstrings and PASV.
42 # Import SOCKS module if it exists, else standard socket module socket
44 import SOCKS
; socket
= SOCKS
49 # Magic number from <socket.h>
50 MSG_OOB
= 0x1 # Process data out of band
53 # The standard FTP server control port
57 # Exception raised when an error or invalid response is received
58 error_reply
= 'ftplib.error_reply' # unexpected [123]xx reply
59 error_temp
= 'ftplib.error_temp' # 4xx errors
60 error_perm
= 'ftplib.error_perm' # 5xx errors
61 error_proto
= 'ftplib.error_proto' # response does not begin with [1-5]
64 # All exceptions (hopefully) that may be raised here and that aren't
65 # (always) programming errors on our side
66 all_errors
= (error_reply
, error_temp
, error_perm
, error_proto
, \
67 socket
.error
, IOError, EOFError)
70 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
77 '''An FTP client class.
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.
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
89 The download/upload functions first issue appropriate TYPE
90 and PORT or PASV commands.
93 # Initialization method (called by class instantiation).
94 # Initialize host to localhost, port to standard ftp port
95 # Optional arguments are host (for connect()),
96 # and user, passwd, acct (for login())
97 def __init__(self
, host
= '', user
= '', passwd
= '', acct
= ''):
98 # Initialize the instance to something mostly harmless
107 resp
= self
.connect(host
)
108 if user
: resp
= self
.login(user
, passwd
, acct
)
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 self
.passiveserver
= 0
117 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
118 self
.sock
.connect((self
.host
, self
.port
))
119 self
.file = self
.sock
.makefile('rb')
120 self
.welcome
= self
.getresp()
123 def getwelcome(self
):
124 '''Get the welcome message from the server.
125 (this is read and squirreled away by connect())'''
127 print '*welcome*', self
.sanitize(self
.welcome
)
130 def set_debuglevel(self
, level
):
131 '''Set the debugging level.
132 The required argument level means:
133 0: no debugging output (default)
134 1: print commands and responses but not body text etc.
135 2: also print raw lines read and sent before stripping CR/LF'''
136 self
.debugging
= level
137 debug
= set_debuglevel
139 def set_pasv(self
, val
):
140 '''Use passive or active mode for data transfers.
141 With a false argument, use the normal PORT mode,
142 With a true argument, use the PASV command.'''
143 self
.passiveserver
= val
145 # Internal: "sanitize" a string for printing
146 def sanitize(self
, s
):
147 if s
[:5] == 'pass ' or s
[:5] == 'PASS ':
149 while i
> 5 and s
[i
-1] in '\r\n':
151 s
= s
[:5] + '*'*(i
-5) + s
[i
:]
154 # Internal: send one line to the server, appending CRLF
155 def putline(self
, line
):
157 if self
.debugging
> 1: print '*put*', self
.sanitize(line
)
160 # Internal: send one command to the server (through putline())
161 def putcmd(self
, line
):
162 if self
.debugging
: print '*cmd*', self
.sanitize(line
)
165 # Internal: return one line from the server, stripping CRLF.
166 # Raise EOFError if the connection is closed
168 line
= self
.file.readline()
169 if self
.debugging
> 1:
170 print '*get*', self
.sanitize(line
)
171 if not line
: raise EOFError
172 if line
[-2:] == CRLF
: line
= line
[:-2]
173 elif line
[-1:] in CRLF
: line
= line
[:-1]
176 # Internal: get a response from the server, which may possibly
177 # consist of multiple lines. Return a single string with no
178 # trailing CRLF. If the response consists of multiple lines,
179 # these are separated by '\n' characters in the string
180 def getmultiline(self
):
181 line
= self
.getline()
185 nextline
= self
.getline()
186 line
= line
+ ('\n' + nextline
)
187 if nextline
[:3] == code
and \
188 nextline
[3:4] <> '-':
192 # Internal: get a response from the server.
193 # Raise various errors if the response indicates an error
195 resp
= self
.getmultiline()
196 if self
.debugging
: print '*resp*', self
.sanitize(resp
)
197 self
.lastresp
= resp
[:3]
200 raise error_temp
, resp
202 raise error_perm
, resp
204 raise error_proto
, resp
208 """Expect a response beginning with '2'."""
209 resp
= self
.getresp()
211 raise error_reply
, resp
215 '''Abort a file transfer. Uses out-of-band data.
216 This does not follow the procedure from the RFC to send Telnet
217 IP and Synch; that doesn't seem to work with the servers I've
218 tried. Instead, just send the ABOR command as OOB data.'''
220 if self
.debugging
> 1: print '*put urgent*', self
.sanitize(line
)
221 self
.sock
.send(line
, MSG_OOB
)
222 resp
= self
.getmultiline()
223 if resp
[:3] not in ('426', '226'):
224 raise error_proto
, resp
226 def sendcmd(self
, cmd
):
227 '''Send a command and return the response.'''
229 return self
.getresp()
231 def voidcmd(self
, cmd
):
232 """Send a command and expect a response beginning with '2'."""
234 return self
.voidresp()
236 def sendport(self
, host
, port
):
237 '''Send a PORT command with the current host and the given port number.'''
238 hbytes
= string
.splitfields(host
, '.')
239 pbytes
= [`port
/256`
, `port
%256`
]
240 bytes
= hbytes
+ pbytes
241 cmd
= 'PORT ' + string
.joinfields(bytes
, ',')
242 return self
.voidcmd(cmd
)
245 '''Create a new socket and send a PORT command for it.'''
247 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
250 dummyhost
, port
= sock
.getsockname() # Get proper port
251 host
, dummyport
= self
.sock
.getsockname() # Get proper host
252 resp
= self
.sendport(host
, port
)
255 def ntransfercmd(self
, cmd
):
256 '''Initiate a transfer over the data connection.
257 If the transfer is active, send a port command and
258 the transfer command, and accept the connection.
259 If the server is passive, send a pasv command, connect
260 to it, and start the transfer command.
261 Either way, return the socket for the connection and
262 the expected size of the transfer. The expected size
263 may be None if it could not be determined.'''
265 if self
.passiveserver
:
266 host
, port
= parse227(self
.sendcmd('PASV'))
267 conn
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
268 conn
.connect((host
, port
))
269 resp
= self
.sendcmd(cmd
)
271 raise error_reply
, resp
273 sock
= self
.makeport()
274 resp
= self
.sendcmd(cmd
)
276 raise error_reply
, resp
277 conn
, sockaddr
= sock
.accept()
278 if resp
[:3] == '150':
279 # this is conditional in case we received a 125
280 size
= parse150(resp
)
283 def transfercmd(self
, cmd
):
284 '''Initiate a transfer over the data connection. Returns
285 the socket for the connection. See also ntransfercmd().'''
286 return self
.ntransfercmd(cmd
)[0]
288 def login(self
, user
= '', passwd
= '', acct
= ''):
289 '''Login, default anonymous.'''
290 if not user
: user
= 'anonymous'
291 if not passwd
: passwd
= ''
292 if not acct
: acct
= ''
293 if user
== 'anonymous' and passwd
in ('', '-'):
294 thishost
= socket
.gethostname()
295 # Make sure it is fully qualified
296 if not '.' in thishost
:
297 thisaddr
= socket
.gethostbyname(thishost
)
298 firstname
, names
, unused
= \
299 socket
.gethostbyaddr(thisaddr
)
300 names
.insert(0, firstname
)
306 if os
.environ
.has_key('LOGNAME'):
307 realuser
= os
.environ
['LOGNAME']
308 elif os
.environ
.has_key('USER'):
309 realuser
= os
.environ
['USER']
311 realuser
= 'anonymous'
312 except AttributeError:
313 # Not all systems have os.environ....
314 realuser
= 'anonymous'
315 passwd
= passwd
+ realuser
+ '@' + thishost
316 resp
= self
.sendcmd('USER ' + user
)
317 if resp
[0] == '3': resp
= self
.sendcmd('PASS ' + passwd
)
318 if resp
[0] == '3': resp
= self
.sendcmd('ACCT ' + acct
)
320 raise error_reply
, resp
323 def retrbinary(self
, cmd
, callback
, blocksize
=8192):
324 '''Retrieve data in binary mode.
325 The argument is a RETR command.
326 The callback function is called for each block.
327 This creates a new port for you'''
328 self
.voidcmd('TYPE I')
329 conn
= self
.transfercmd(cmd
)
331 data
= conn
.recv(blocksize
)
336 return self
.voidresp()
338 def retrlines(self
, cmd
, callback
= None):
339 '''Retrieve data in line mode.
340 The argument is a RETR or LIST command.
341 The callback function (2nd argument) is called for each line,
342 with trailing CRLF stripped. This creates a new port for you.
343 print_line() is the default callback.'''
344 if not callback
: callback
= print_line
345 resp
= self
.sendcmd('TYPE A')
346 conn
= self
.transfercmd(cmd
)
347 fp
= conn
.makefile('rb')
350 if self
.debugging
> 2: print '*retr*', `line`
353 if line
[-2:] == CRLF
:
355 elif line
[-1:] == '\n':
360 return self
.voidresp()
362 def storbinary(self
, cmd
, fp
, blocksize
):
363 '''Store a file in binary mode.'''
364 self
.voidcmd('TYPE I')
365 conn
= self
.transfercmd(cmd
)
367 buf
= fp
.read(blocksize
)
371 return self
.voidresp()
373 def storlines(self
, cmd
, fp
):
374 '''Store a file in line mode.'''
375 self
.voidcmd('TYPE A')
376 conn
= self
.transfercmd(cmd
)
381 if buf
[-1] in CRLF
: buf
= buf
[:-1]
385 return self
.voidresp()
387 def acct(self
, password
):
388 '''Send new account name.'''
389 cmd
= 'ACCT ' + password
390 return self
.voidcmd(cmd
)
392 def nlst(self
, *args
):
393 '''Return a list of files in a given directory (default the current).'''
396 cmd
= cmd
+ (' ' + arg
)
398 self
.retrlines(cmd
, files
.append
)
401 def dir(self
, *args
):
402 '''List a directory in long form.
403 By default list current directory to stdout.
404 Optional last argument is callback function; all
405 non-empty arguments before it are concatenated to the
406 LIST command. (This *should* only be used for a pathname.)'''
409 if args
[-1:] and type(args
[-1]) != type(''):
410 args
, func
= args
[:-1], args
[-1]
413 cmd
= cmd
+ (' ' + arg
)
414 self
.retrlines(cmd
, func
)
416 def rename(self
, fromname
, toname
):
418 resp
= self
.sendcmd('RNFR ' + fromname
)
420 raise error_reply
, resp
421 return self
.voidcmd('RNTO ' + toname
)
423 def delete(self
, filename
):
425 resp
= self
.sendcmd('DELE ' + filename
)
426 if resp
[:3] in ('250', '200'):
428 elif resp
[:1] == '5':
429 raise error_perm
, resp
431 raise error_reply
, resp
433 def cwd(self
, dirname
):
434 '''Change to a directory.'''
437 return self
.voidcmd('CDUP')
438 except error_perm
, msg
:
440 raise error_perm
, msg
442 dirname
= '.' # does nothing, but could return error
443 cmd
= 'CWD ' + dirname
444 return self
.voidcmd(cmd
)
446 def size(self
, filename
):
447 '''Retrieve the size of a file.'''
448 # Note that the RFC doesn't say anything about 'SIZE'
449 resp
= self
.sendcmd('SIZE ' + filename
)
450 if resp
[:3] == '213':
451 return string
.atoi(string
.strip(resp
[3:]))
453 def mkd(self
, dirname
):
454 '''Make a directory, return its full pathname.'''
455 resp
= self
.sendcmd('MKD ' + dirname
)
456 return parse257(resp
)
458 def rmd(self
, dirname
):
459 '''Remove a directory.'''
460 return self
.voidcmd('RMD ' + dirname
)
463 '''Return current working directory.'''
464 resp
= self
.sendcmd('PWD')
465 return parse257(resp
)
468 '''Quit, and close the connection.'''
469 resp
= self
.voidcmd('QUIT')
474 '''Close the connection without assuming anything about it.'''
477 del self
.file, self
.sock
483 '''Parse the '150' response for a RETR request.
484 Returns the expected transfer size or None; size is not guaranteed to
485 be present in the 150 message.
487 if resp
[:3] != '150':
488 raise error_reply
, resp
492 _150_re
= re
.compile("150 .* \((\d+) bytes\)", re
.IGNORECASE
)
493 m
= _150_re
.match(resp
)
495 return string
.atoi(m
.group(1))
500 '''Parse the '227' response for a PASV request.
501 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
502 Return ('host.addr.as.numbers', port#) tuple.'''
504 if resp
[:3] <> '227':
505 raise error_reply
, resp
506 left
= string
.find(resp
, '(')
507 if left
< 0: raise error_proto
, resp
508 right
= string
.find(resp
, ')', left
+ 1)
510 raise error_proto
, resp
# should contain '(h1,h2,h3,h4,p1,p2)'
511 numbers
= string
.split(resp
[left
+1:right
], ',')
512 if len(numbers
) <> 6:
513 raise error_proto
, resp
514 host
= string
.join(numbers
[:4], '.')
515 port
= (string
.atoi(numbers
[4]) << 8) + string
.atoi(numbers
[5])
520 '''Parse the '257' response for a MKD or PWD request.
521 This is a response to a MKD or PWD request: a directory name.
522 Returns the directoryname in the 257 reply.'''
524 if resp
[:3] <> '257':
525 raise error_reply
, resp
526 if resp
[3:5] <> ' "':
527 return '' # Not compliant to RFC 959, but UNIX ftpd does this
535 if i
>= n
or resp
[i
] <> '"':
538 dirname
= dirname
+ c
542 def print_line(line
):
543 '''Default retrlines callback to print a line.'''
547 def ftpcp(source
, sourcename
, target
, targetname
= '', type = 'I'):
548 '''Copy file from one FTP-instance to another.'''
549 if not targetname
: targetname
= sourcename
550 type = 'TYPE ' + type
553 sourcehost
, sourceport
= parse227(source
.sendcmd('PASV'))
554 target
.sendport(sourcehost
, sourceport
)
555 # RFC 959: the user must "listen" [...] BEFORE sending the
557 # So: STOR before RETR, because here the target is a "user".
558 treply
= target
.sendcmd('STOR ' + targetname
)
559 if treply
[:3] not in ('125', '150'): raise error_proto
# RFC 959
560 sreply
= source
.sendcmd('RETR ' + sourcename
)
561 if sreply
[:3] not in ('125', '150'): raise error_proto
# RFC 959
567 """Class to parse & provide access to 'netrc' format files.
569 See the netrc(4) man page for information on the file format.
571 WARNING: This class is obsolete -- use module netrc instead.
578 def __init__(self
, filename
=None):
580 if os
.environ
.has_key("HOME"):
581 filename
= os
.path
.join(os
.environ
["HOME"],
585 "specify file to load or set $HOME"
588 fp
= open(filename
, "r")
593 if in_macro
and string
.strip(line
):
594 macro_lines
.append(line
)
597 self
.__macros
[macro_name
] = tuple(macro_lines
)
599 words
= string
.split(line
)
600 host
= user
= passwd
= acct
= None
603 while i
< len(words
):
611 elif w1
== 'machine' and w2
:
612 host
= string
.lower(w2
)
614 elif w1
== 'login' and w2
:
617 elif w1
== 'password' and w2
:
620 elif w1
== 'account' and w2
:
623 elif w1
== 'macdef' and w2
:
630 self
.__defuser
= user
or self
.__defuser
631 self
.__defpasswd
= passwd
or self
.__defpasswd
632 self
.__defacct
= acct
or self
.__defacct
634 if self
.__hosts
.has_key(host
):
635 ouser
, opasswd
, oacct
= \
638 passwd
= passwd
or opasswd
640 self
.__hosts
[host
] = user
, passwd
, acct
644 """Return a list of hosts mentioned in the .netrc file."""
645 return self
.__hosts
.keys()
647 def get_account(self
, host
):
648 """Returns login information for the named host.
650 The return value is a triple containing userid,
651 password, and the accounting field.
654 host
= string
.lower(host
)
655 user
= passwd
= acct
= None
656 if self
.__hosts
.has_key(host
):
657 user
, passwd
, acct
= self
.__hosts
[host
]
658 user
= user
or self
.__defuser
659 passwd
= passwd
or self
.__defpasswd
660 acct
= acct
or self
.__defacct
661 return user
, passwd
, acct
663 def get_macros(self
):
664 """Return a list of all defined macro names."""
665 return self
.__macros
.keys()
667 def get_macro(self
, macro
):
668 """Return a sequence of lines which define a named macro."""
669 return self
.__macros
[macro
]
675 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
679 while sys
.argv
[1] == '-d':
680 debugging
= debugging
+1
682 if sys
.argv
[1][:2] == '-r':
683 # get name of alternate ~/.netrc file:
684 rcfile
= sys
.argv
[1][2:]
688 ftp
.set_debuglevel(debugging
)
689 userid
= passwd
= acct
= ''
691 netrc
= Netrc(rcfile
)
693 if rcfile
is not None:
694 sys
.stderr
.write("Could not open account file"
695 " -- using anonymous login.")
698 userid
, passwd
, acct
= netrc
.get_account(host
)
700 # no account for host
702 "No account -- using anonymous login.")
703 ftp
.login(userid
, passwd
, acct
)
704 for file in sys
.argv
[2:]:
707 elif file[:2] == '-d':
709 if file[2:]: cmd
= cmd
+ ' ' + file[2:]
710 resp
= ftp
.sendcmd(cmd
)
712 ftp
.set_pasv(not ftp
.passiveserver
)
714 ftp
.retrbinary('RETR ' + file, \
715 sys
.stdout
.write
, 1024)
719 if __name__
== '__main__':