blob: a15c412f0a33c89af90ad6b2993c7dbf7917944c [file] [log] [blame]
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +00001"""An FTP client class and some helper functions.
2
Guido van Rossumd2560b01996-05-28 23:41:25 +00003Based on RFC 959: File Transfer Protocol
4(FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00005
Guido van Rossumd2560b01996-05-28 23:41:25 +00006Example:
7
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
Guido van Rossum2f3941d1997-10-07 14:49:56 +000011'230 Guest login ok, access restrictions apply.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000012>>> ftp.retrlines('LIST') # list directory contents
13total 9
14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
15drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
17drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
18d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
19drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
20drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
21drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
22-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
Guido van Rossum2f3941d1997-10-07 14:49:56 +000023'226 Transfer complete.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000024>>> ftp.quit()
Guido van Rossum2f3941d1997-10-07 14:49:56 +000025'221 Goodbye.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000026>>>
27
28A nice test that reveals some of the network dialogue would be:
29python ftplib.py -d localhost -l -p -l
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000030"""
Guido van Rossumc567c601992-11-05 22:22:37 +000031
Guido van Rossum98d9fd32000-02-28 15:12:25 +000032#
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.
36#
Guido van Rossumc567c601992-11-05 22:22:37 +000037
Guido van Rossum1115ab21992-11-04 15:51:30 +000038import os
39import sys
Guido van Rossum1115ab21992-11-04 15:51:30 +000040import string
41
Guido van Rossumb6775db1994-08-01 11:34:53 +000042# Import SOCKS module if it exists, else standard socket module socket
43try:
Peter Schneider-Kamp2d2785a2000-08-16 20:30:21 +000044 import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
45 from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
Guido van Rossumb6775db1994-08-01 11:34:53 +000046except ImportError:
Guido van Rossum8ca84201998-03-26 20:56:10 +000047 import socket
Guido van Rossumb6775db1994-08-01 11:34:53 +000048
Guido van Rossum1115ab21992-11-04 15:51:30 +000049
Guido van Rossumd3166071993-05-24 14:16:22 +000050# Magic number from <socket.h>
51MSG_OOB = 0x1 # Process data out of band
52
53
Guido van Rossumc567c601992-11-05 22:22:37 +000054# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000055FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000056
57
Guido van Rossum21974791992-11-06 13:34:17 +000058# Exception raised when an error or invalid response is received
Fred Drake227b1202000-08-17 05:06:49 +000059class Error(Exception): pass
60class error_reply(Error): pass # unexpected [123]xx reply
61class error_temp(Error): pass # 4xx errors
62class error_perm(Error): pass # 5xx errors
63class error_proto(Error): pass # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000064
65
Guido van Rossum21974791992-11-06 13:34:17 +000066# All exceptions (hopefully) that may be raised here and that aren't
67# (always) programming errors on our side
Fred Drake227b1202000-08-17 05:06:49 +000068all_errors = (Error, socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000069
70
Guido van Rossum1115ab21992-11-04 15:51:30 +000071# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
72CRLF = '\r\n'
73
74
Guido van Rossum1115ab21992-11-04 15:51:30 +000075# The class itself
76class FTP:
77
Guido van Rossumd2560b01996-05-28 23:41:25 +000078 '''An FTP client class.
79
80 To create a connection, call the class using these argument:
81 host, user, passwd, acct
82 These are all strings, and have default value ''.
83 Then use self.connect() with optional host and port argument.
84
85 To download a file, use ftp.retrlines('RETR ' + filename),
86 or ftp.retrbinary() with slightly different arguments.
87 To upload a file, use ftp.storlines() or ftp.storbinary(),
88 which have an open file as argument (see their definitions
89 below for details).
90 The download/upload functions first issue appropriate TYPE
91 and PORT or PASV commands.
92'''
93
94 # Initialization method (called by class instantiation).
Guido van Rossum52fc1f61993-06-17 12:38:10 +000095 # Initialize host to localhost, port to standard ftp port
Guido van Rossumae3b3a31993-11-30 13:43:54 +000096 # Optional arguments are host (for connect()),
97 # and user, passwd, acct (for login())
Guido van Rossumb6775db1994-08-01 11:34:53 +000098 def __init__(self, host = '', user = '', passwd = '', acct = ''):
Guido van Rossum52fc1f61993-06-17 12:38:10 +000099 # Initialize the instance to something mostly harmless
Guido van Rossum1115ab21992-11-04 15:51:30 +0000100 self.debugging = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000101 self.host = ''
102 self.port = FTP_PORT
103 self.sock = None
104 self.file = None
105 self.welcome = None
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000106 resp = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000107 if host:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000108 resp = self.connect(host)
109 if user: resp = self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000110
Guido van Rossumb6775db1994-08-01 11:34:53 +0000111 def connect(self, host = '', port = 0):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000112 '''Connect to host. Arguments are:
113 - host: hostname to connect to (string, default previous host)
114 - port: port to connect to (integer, default previous port)'''
Guido van Rossumb6775db1994-08-01 11:34:53 +0000115 if host: self.host = host
116 if port: self.port = port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000117 self.passiveserver = 0
Guido van Rossum1115ab21992-11-04 15:51:30 +0000118 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000119 self.sock.connect((self.host, self.port))
Guido van Rossum24611f81996-09-30 22:02:50 +0000120 self.file = self.sock.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000121 self.welcome = self.getresp()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000122 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000123
Guido van Rossum1115ab21992-11-04 15:51:30 +0000124 def getwelcome(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000125 '''Get the welcome message from the server.
126 (this is read and squirreled away by connect())'''
Guido van Rossumebaf1041995-05-05 15:54:14 +0000127 if self.debugging:
128 print '*welcome*', self.sanitize(self.welcome)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000129 return self.welcome
130
Guido van Rossume65cce51993-11-08 15:05:21 +0000131 def set_debuglevel(self, level):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000132 '''Set the debugging level.
133 The required argument level means:
134 0: no debugging output (default)
135 1: print commands and responses but not body text etc.
136 2: also print raw lines read and sent before stripping CR/LF'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000137 self.debugging = level
Guido van Rossume65cce51993-11-08 15:05:21 +0000138 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000139
Guido van Rossumd2560b01996-05-28 23:41:25 +0000140 def set_pasv(self, val):
141 '''Use passive or active mode for data transfers.
142 With a false argument, use the normal PORT mode,
143 With a true argument, use the PASV command.'''
144 self.passiveserver = val
145
Guido van Rossumebaf1041995-05-05 15:54:14 +0000146 # Internal: "sanitize" a string for printing
147 def sanitize(self, s):
148 if s[:5] == 'pass ' or s[:5] == 'PASS ':
149 i = len(s)
150 while i > 5 and s[i-1] in '\r\n':
151 i = i-1
152 s = s[:5] + '*'*(i-5) + s[i:]
153 return `s`
154
Guido van Rossum1115ab21992-11-04 15:51:30 +0000155 # Internal: send one line to the server, appending CRLF
156 def putline(self, line):
157 line = line + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000158 if self.debugging > 1: print '*put*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000159 self.sock.send(line)
160
161 # Internal: send one command to the server (through putline())
162 def putcmd(self, line):
Guido van Rossumebaf1041995-05-05 15:54:14 +0000163 if self.debugging: print '*cmd*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000164 self.putline(line)
165
166 # Internal: return one line from the server, stripping CRLF.
167 # Raise EOFError if the connection is closed
168 def getline(self):
169 line = self.file.readline()
170 if self.debugging > 1:
Guido van Rossumebaf1041995-05-05 15:54:14 +0000171 print '*get*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000172 if not line: raise EOFError
173 if line[-2:] == CRLF: line = line[:-2]
174 elif line[-1:] in CRLF: line = line[:-1]
175 return line
176
177 # Internal: get a response from the server, which may possibly
178 # consist of multiple lines. Return a single string with no
179 # trailing CRLF. If the response consists of multiple lines,
180 # these are separated by '\n' characters in the string
181 def getmultiline(self):
182 line = self.getline()
183 if line[3:4] == '-':
184 code = line[:3]
185 while 1:
186 nextline = self.getline()
187 line = line + ('\n' + nextline)
188 if nextline[:3] == code and \
189 nextline[3:4] <> '-':
190 break
191 return line
192
193 # Internal: get a response from the server.
194 # Raise various errors if the response indicates an error
195 def getresp(self):
196 resp = self.getmultiline()
Guido van Rossumebaf1041995-05-05 15:54:14 +0000197 if self.debugging: print '*resp*', self.sanitize(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000198 self.lastresp = resp[:3]
199 c = resp[:1]
200 if c == '4':
Guido van Rossumc567c601992-11-05 22:22:37 +0000201 raise error_temp, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000202 if c == '5':
Guido van Rossumc567c601992-11-05 22:22:37 +0000203 raise error_perm, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000204 if c not in '123':
Guido van Rossumc567c601992-11-05 22:22:37 +0000205 raise error_proto, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000206 return resp
207
Guido van Rossumc567c601992-11-05 22:22:37 +0000208 def voidresp(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000209 """Expect a response beginning with '2'."""
Guido van Rossumc567c601992-11-05 22:22:37 +0000210 resp = self.getresp()
211 if resp[0] <> '2':
212 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000213 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000214
Guido van Rossumd3166071993-05-24 14:16:22 +0000215 def abort(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000216 '''Abort a file transfer. Uses out-of-band data.
217 This does not follow the procedure from the RFC to send Telnet
218 IP and Synch; that doesn't seem to work with the servers I've
219 tried. Instead, just send the ABOR command as OOB data.'''
Guido van Rossumd3166071993-05-24 14:16:22 +0000220 line = 'ABOR' + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000221 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
Guido van Rossumd3166071993-05-24 14:16:22 +0000222 self.sock.send(line, MSG_OOB)
223 resp = self.getmultiline()
224 if resp[:3] not in ('426', '226'):
225 raise error_proto, resp
226
Guido van Rossum1115ab21992-11-04 15:51:30 +0000227 def sendcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000228 '''Send a command and return the response.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000229 self.putcmd(cmd)
230 return self.getresp()
231
Guido van Rossumc567c601992-11-05 22:22:37 +0000232 def voidcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000233 """Send a command and expect a response beginning with '2'."""
Guido van Rossumc68a4011992-11-05 23:01:42 +0000234 self.putcmd(cmd)
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000235 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000236
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000237 def sendport(self, host, port):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000238 '''Send a PORT command with the current host and the given port number.'''
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000239 hbytes = string.splitfields(host, '.')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000240 pbytes = [`port/256`, `port%256`]
241 bytes = hbytes + pbytes
242 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000243 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000244
Guido van Rossum1115ab21992-11-04 15:51:30 +0000245 def makeport(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000246 '''Create a new socket and send a PORT command for it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000247 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000248 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum303c1791995-06-20 17:21:42 +0000249 sock.bind(('', 0))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000250 sock.listen(1)
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000251 dummyhost, port = sock.getsockname() # Get proper port
252 host, dummyport = self.sock.getsockname() # Get proper host
253 resp = self.sendport(host, port)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000254 return sock
255
Fred Drake4de02d91997-01-10 18:26:09 +0000256 def ntransfercmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000257 '''Initiate a transfer over the data connection.
258 If the transfer is active, send a port command and
259 the transfer command, and accept the connection.
260 If the server is passive, send a pasv command, connect
261 to it, and start the transfer command.
Fred Drake4de02d91997-01-10 18:26:09 +0000262 Either way, return the socket for the connection and
263 the expected size of the transfer. The expected size
264 may be None if it could not be determined.'''
265 size = None
Guido van Rossumd2560b01996-05-28 23:41:25 +0000266 if self.passiveserver:
267 host, port = parse227(self.sendcmd('PASV'))
268 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000269 conn.connect((host, port))
Guido van Rossumd2560b01996-05-28 23:41:25 +0000270 resp = self.sendcmd(cmd)
271 if resp[0] <> '1':
272 raise error_reply, resp
273 else:
274 sock = self.makeport()
275 resp = self.sendcmd(cmd)
276 if resp[0] <> '1':
277 raise error_reply, resp
278 conn, sockaddr = sock.accept()
Fred Drake4de02d91997-01-10 18:26:09 +0000279 if resp[:3] == '150':
280 # this is conditional in case we received a 125
281 size = parse150(resp)
282 return conn, size
283
284 def transfercmd(self, cmd):
285 '''Initiate a transfer over the data connection. Returns
286 the socket for the connection. See also ntransfercmd().'''
287 return self.ntransfercmd(cmd)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000288
Guido van Rossumb6775db1994-08-01 11:34:53 +0000289 def login(self, user = '', passwd = '', acct = ''):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000290 '''Login, default anonymous.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000291 if not user: user = 'anonymous'
Guido van Rossum98245091998-02-19 21:15:44 +0000292 if not passwd: passwd = ''
293 if not acct: acct = ''
Guido van Rossumc567c601992-11-05 22:22:37 +0000294 if user == 'anonymous' and passwd in ('', '-'):
Peter Schneider-Kamp2d2785a2000-08-16 20:30:21 +0000295 # get fully qualified domain name of local host
296 thishost = socket.getfqdn()
Jack Jansen40b98351995-01-19 12:24:45 +0000297 try:
298 if os.environ.has_key('LOGNAME'):
299 realuser = os.environ['LOGNAME']
300 elif os.environ.has_key('USER'):
301 realuser = os.environ['USER']
302 else:
303 realuser = 'anonymous'
304 except AttributeError:
305 # Not all systems have os.environ....
Guido van Rossumc567c601992-11-05 22:22:37 +0000306 realuser = 'anonymous'
307 passwd = passwd + realuser + '@' + thishost
308 resp = self.sendcmd('USER ' + user)
309 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
310 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
311 if resp[0] <> '2':
312 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000313 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000314
Guido van Rossumab76af31997-12-03 19:34:14 +0000315 def retrbinary(self, cmd, callback, blocksize=8192):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000316 '''Retrieve data in binary mode.
317 The argument is a RETR command.
318 The callback function is called for each block.
319 This creates a new port for you'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000320 self.voidcmd('TYPE I')
321 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000322 while 1:
323 data = conn.recv(blocksize)
324 if not data:
325 break
326 callback(data)
327 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000328 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000329
Guido van Rossumb6775db1994-08-01 11:34:53 +0000330 def retrlines(self, cmd, callback = None):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000331 '''Retrieve data in line mode.
332 The argument is a RETR or LIST command.
333 The callback function (2nd argument) is called for each line,
334 with trailing CRLF stripped. This creates a new port for you.
Fred Draked5f173b1999-07-07 13:36:59 +0000335 print_line() is the default callback.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000336 if not callback: callback = print_line
Guido van Rossumc567c601992-11-05 22:22:37 +0000337 resp = self.sendcmd('TYPE A')
338 conn = self.transfercmd(cmd)
Guido van Rossum24611f81996-09-30 22:02:50 +0000339 fp = conn.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000340 while 1:
341 line = fp.readline()
Guido van Rossumc0e68d11995-09-30 16:51:50 +0000342 if self.debugging > 2: print '*retr*', `line`
Guido van Rossum1115ab21992-11-04 15:51:30 +0000343 if not line:
344 break
345 if line[-2:] == CRLF:
346 line = line[:-2]
Guido van Rossumc6769c51998-12-21 16:26:31 +0000347 elif line[-1:] == '\n':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000348 line = line[:-1]
349 callback(line)
350 fp.close()
351 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000352 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000353
Guido van Rossumc567c601992-11-05 22:22:37 +0000354 def storbinary(self, cmd, fp, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000355 '''Store a file in binary mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000356 self.voidcmd('TYPE I')
357 conn = self.transfercmd(cmd)
358 while 1:
359 buf = fp.read(blocksize)
360 if not buf: break
361 conn.send(buf)
362 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000363 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000364
Guido van Rossumc567c601992-11-05 22:22:37 +0000365 def storlines(self, cmd, fp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000366 '''Store a file in line mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000367 self.voidcmd('TYPE A')
368 conn = self.transfercmd(cmd)
369 while 1:
370 buf = fp.readline()
371 if not buf: break
372 if buf[-2:] <> CRLF:
373 if buf[-1] in CRLF: buf = buf[:-1]
374 buf = buf + CRLF
375 conn.send(buf)
376 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000377 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000378
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000379 def acct(self, password):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000380 '''Send new account name.'''
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000381 cmd = 'ACCT ' + password
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000382 return self.voidcmd(cmd)
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000383
Guido van Rossumc567c601992-11-05 22:22:37 +0000384 def nlst(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000385 '''Return a list of files in a given directory (default the current).'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000386 cmd = 'NLST'
387 for arg in args:
388 cmd = cmd + (' ' + arg)
389 files = []
390 self.retrlines(cmd, files.append)
391 return files
392
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000393 def dir(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000394 '''List a directory in long form.
395 By default list current directory to stdout.
396 Optional last argument is callback function; all
397 non-empty arguments before it are concatenated to the
398 LIST command. (This *should* only be used for a pathname.)'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000399 cmd = 'LIST'
400 func = None
401 if args[-1:] and type(args[-1]) != type(''):
402 args, func = args[:-1], args[-1]
403 for arg in args:
404 if arg:
405 cmd = cmd + (' ' + arg)
406 self.retrlines(cmd, func)
407
Guido van Rossumc567c601992-11-05 22:22:37 +0000408 def rename(self, fromname, toname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000409 '''Rename a file.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000410 resp = self.sendcmd('RNFR ' + fromname)
411 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000412 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000413 return self.voidcmd('RNTO ' + toname)
Guido van Rossumc567c601992-11-05 22:22:37 +0000414
Guido van Rossum8ca84201998-03-26 20:56:10 +0000415 def delete(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000416 '''Delete a file.'''
Guido van Rossum8ca84201998-03-26 20:56:10 +0000417 resp = self.sendcmd('DELE ' + filename)
Guido van Rossum6bbd1d01998-07-02 20:41:20 +0000418 if resp[:3] in ('250', '200'):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000419 return resp
420 elif resp[:1] == '5':
421 raise error_perm, resp
422 else:
423 raise error_reply, resp
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000424
Guido van Rossum02cf5821993-05-17 08:00:02 +0000425 def cwd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000426 '''Change to a directory.'''
Guido van Rossumdf563861993-07-06 15:19:36 +0000427 if dirname == '..':
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000428 try:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000429 return self.voidcmd('CDUP')
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000430 except error_perm, msg:
431 if msg[:3] != '500':
432 raise error_perm, msg
Guido van Rossum1ebcf6a1999-08-18 21:51:10 +0000433 elif dirname == '':
434 dirname = '.' # does nothing, but could return error
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000435 cmd = 'CWD ' + dirname
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000436 return self.voidcmd(cmd)
Guido van Rossum02cf5821993-05-17 08:00:02 +0000437
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000438 def size(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000439 '''Retrieve the size of a file.'''
440 # Note that the RFC doesn't say anything about 'SIZE'
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000441 resp = self.sendcmd('SIZE ' + filename)
442 if resp[:3] == '213':
443 return string.atoi(string.strip(resp[3:]))
444
Guido van Rossumc567c601992-11-05 22:22:37 +0000445 def mkd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000446 '''Make a directory, return its full pathname.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000447 resp = self.sendcmd('MKD ' + dirname)
448 return parse257(resp)
449
Guido van Rossum98245091998-02-19 21:15:44 +0000450 def rmd(self, dirname):
451 '''Remove a directory.'''
452 return self.voidcmd('RMD ' + dirname)
453
Guido van Rossumc567c601992-11-05 22:22:37 +0000454 def pwd(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000455 '''Return current working directory.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000456 resp = self.sendcmd('PWD')
457 return parse257(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000458
Guido van Rossum1115ab21992-11-04 15:51:30 +0000459 def quit(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000460 '''Quit, and close the connection.'''
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000461 resp = self.voidcmd('QUIT')
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000462 self.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000463 return resp
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000464
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000465 def close(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000466 '''Close the connection without assuming anything about it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000467 self.file.close()
468 self.sock.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000469 del self.file, self.sock
470
471
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000472_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000473
474def parse150(resp):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000475 '''Parse the '150' response for a RETR request.
476 Returns the expected transfer size or None; size is not guaranteed to
477 be present in the 150 message.
478 '''
479 if resp[:3] != '150':
480 raise error_reply, resp
481 global _150_re
482 if _150_re is None:
483 import re
Fred Drake9291d271998-04-27 14:39:44 +0000484 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000485 m = _150_re.match(resp)
486 if m:
487 return string.atoi(m.group(1))
488 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000489
490
Guido van Rossumd2560b01996-05-28 23:41:25 +0000491def parse227(resp):
492 '''Parse the '227' response for a PASV request.
493 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
494 Return ('host.addr.as.numbers', port#) tuple.'''
495
496 if resp[:3] <> '227':
497 raise error_reply, resp
498 left = string.find(resp, '(')
499 if left < 0: raise error_proto, resp
500 right = string.find(resp, ')', left + 1)
501 if right < 0:
502 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
503 numbers = string.split(resp[left+1:right], ',')
504 if len(numbers) <> 6:
505 raise error_proto, resp
506 host = string.join(numbers[:4], '.')
507 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
508 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000509
510
Guido van Rossumc567c601992-11-05 22:22:37 +0000511def parse257(resp):
Guido van Rossum98245091998-02-19 21:15:44 +0000512 '''Parse the '257' response for a MKD or PWD request.
513 This is a response to a MKD or PWD request: a directory name.
Guido van Rossumd2560b01996-05-28 23:41:25 +0000514 Returns the directoryname in the 257 reply.'''
515
Guido van Rossumc567c601992-11-05 22:22:37 +0000516 if resp[:3] <> '257':
517 raise error_reply, resp
518 if resp[3:5] <> ' "':
519 return '' # Not compliant to RFC 959, but UNIX ftpd does this
520 dirname = ''
521 i = 5
522 n = len(resp)
523 while i < n:
524 c = resp[i]
525 i = i+1
526 if c == '"':
527 if i >= n or resp[i] <> '"':
528 break
529 i = i+1
530 dirname = dirname + c
531 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000532
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000533
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000534def print_line(line):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000535 '''Default retrlines callback to print a line.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000536 print line
537
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000538
Guido van Rossumd2560b01996-05-28 23:41:25 +0000539def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
540 '''Copy file from one FTP-instance to another.'''
541 if not targetname: targetname = sourcename
542 type = 'TYPE ' + type
543 source.voidcmd(type)
544 target.voidcmd(type)
545 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
546 target.sendport(sourcehost, sourceport)
547 # RFC 959: the user must "listen" [...] BEFORE sending the
548 # transfer request.
549 # So: STOR before RETR, because here the target is a "user".
550 treply = target.sendcmd('STOR ' + targetname)
551 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
552 sreply = source.sendcmd('RETR ' + sourcename)
553 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
554 source.voidresp()
555 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000556
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000557
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000558class Netrc:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000559 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000560
Guido van Rossum8ca84201998-03-26 20:56:10 +0000561 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000562
Guido van Rossumc822a451998-12-22 16:49:16 +0000563 WARNING: This class is obsolete -- use module netrc instead.
564
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000565 """
Guido van Rossum8ca84201998-03-26 20:56:10 +0000566 __defuser = None
567 __defpasswd = None
568 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000569
Guido van Rossum8ca84201998-03-26 20:56:10 +0000570 def __init__(self, filename=None):
571 if not filename:
572 if os.environ.has_key("HOME"):
573 filename = os.path.join(os.environ["HOME"],
574 ".netrc")
575 else:
576 raise IOError, \
577 "specify file to load or set $HOME"
578 self.__hosts = {}
579 self.__macros = {}
580 fp = open(filename, "r")
581 in_macro = 0
582 while 1:
583 line = fp.readline()
584 if not line: break
585 if in_macro and string.strip(line):
586 macro_lines.append(line)
587 continue
588 elif in_macro:
589 self.__macros[macro_name] = tuple(macro_lines)
590 in_macro = 0
591 words = string.split(line)
592 host = user = passwd = acct = None
593 default = 0
594 i = 0
595 while i < len(words):
596 w1 = words[i]
597 if i+1 < len(words):
598 w2 = words[i + 1]
599 else:
600 w2 = None
601 if w1 == 'default':
602 default = 1
603 elif w1 == 'machine' and w2:
604 host = string.lower(w2)
605 i = i + 1
606 elif w1 == 'login' and w2:
607 user = w2
608 i = i + 1
609 elif w1 == 'password' and w2:
610 passwd = w2
611 i = i + 1
612 elif w1 == 'account' and w2:
613 acct = w2
614 i = i + 1
615 elif w1 == 'macdef' and w2:
616 macro_name = w2
617 macro_lines = []
618 in_macro = 1
619 break
620 i = i + 1
621 if default:
622 self.__defuser = user or self.__defuser
623 self.__defpasswd = passwd or self.__defpasswd
624 self.__defacct = acct or self.__defacct
625 if host:
626 if self.__hosts.has_key(host):
627 ouser, opasswd, oacct = \
628 self.__hosts[host]
629 user = user or ouser
630 passwd = passwd or opasswd
631 acct = acct or oacct
632 self.__hosts[host] = user, passwd, acct
633 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000634
Guido van Rossum8ca84201998-03-26 20:56:10 +0000635 def get_hosts(self):
636 """Return a list of hosts mentioned in the .netrc file."""
637 return self.__hosts.keys()
638
639 def get_account(self, host):
640 """Returns login information for the named host.
641
642 The return value is a triple containing userid,
643 password, and the accounting field.
644
645 """
646 host = string.lower(host)
647 user = passwd = acct = None
648 if self.__hosts.has_key(host):
649 user, passwd, acct = self.__hosts[host]
650 user = user or self.__defuser
651 passwd = passwd or self.__defpasswd
652 acct = acct or self.__defacct
653 return user, passwd, acct
654
655 def get_macros(self):
656 """Return a list of all defined macro names."""
657 return self.__macros.keys()
658
659 def get_macro(self, macro):
660 """Return a sequence of lines which define a named macro."""
661 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000662
Fred Drake475d51d1997-06-24 22:02:54 +0000663
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000664
Guido van Rossum1115ab21992-11-04 15:51:30 +0000665def test():
Guido van Rossumd2560b01996-05-28 23:41:25 +0000666 '''Test program.
Fred Drake475d51d1997-06-24 22:02:54 +0000667 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000668
Guido van Rossumb6775db1994-08-01 11:34:53 +0000669 debugging = 0
Fred Drake475d51d1997-06-24 22:02:54 +0000670 rcfile = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000671 while sys.argv[1] == '-d':
672 debugging = debugging+1
673 del sys.argv[1]
Fred Drake475d51d1997-06-24 22:02:54 +0000674 if sys.argv[1][:2] == '-r':
675 # get name of alternate ~/.netrc file:
676 rcfile = sys.argv[1][2:]
677 del sys.argv[1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000678 host = sys.argv[1]
679 ftp = FTP(host)
680 ftp.set_debuglevel(debugging)
Fred Drake475d51d1997-06-24 22:02:54 +0000681 userid = passwd = acct = ''
682 try:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000683 netrc = Netrc(rcfile)
Fred Drake475d51d1997-06-24 22:02:54 +0000684 except IOError:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000685 if rcfile is not None:
686 sys.stderr.write("Could not open account file"
687 " -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000688 else:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000689 try:
690 userid, passwd, acct = netrc.get_account(host)
691 except KeyError:
692 # no account for host
693 sys.stderr.write(
694 "No account -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000695 ftp.login(userid, passwd, acct)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000696 for file in sys.argv[2:]:
697 if file[:2] == '-l':
698 ftp.dir(file[2:])
699 elif file[:2] == '-d':
700 cmd = 'CWD'
701 if file[2:]: cmd = cmd + ' ' + file[2:]
702 resp = ftp.sendcmd(cmd)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000703 elif file == '-p':
704 ftp.set_pasv(not ftp.passiveserver)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000705 else:
706 ftp.retrbinary('RETR ' + file, \
707 sys.stdout.write, 1024)
708 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000709
710
711if __name__ == '__main__':
712 test()