blob: fda7864a694ccf728d03d1a67448e9e7b0407391 [file] [log] [blame]
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +00001"""An FTP client class and some helper functions.
2
Barry Warsaw100d81e2000-09-01 06:09:23 +00003Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00004
Guido van Rossumd2560b01996-05-28 23:41:25 +00005Example:
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 user@hostname
Guido van Rossum2f3941d1997-10-07 14:49:56 +000010'230 Guest login ok, access restrictions apply.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000011>>> ftp.retrlines('LIST') # list directory contents
12total 9
13drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
15drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
17d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
18drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
19drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
20drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
21-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
Guido van Rossum2f3941d1997-10-07 14:49:56 +000022'226 Transfer complete.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000023>>> ftp.quit()
Guido van Rossum2f3941d1997-10-07 14:49:56 +000024'221 Goodbye.'
Tim Peters88869f92001-01-14 23:36:06 +000025>>>
Guido van Rossumd2560b01996-05-28 23:41:25 +000026
27A nice test that reveals some of the network dialogue would be:
28python ftplib.py -d localhost -l -p -l
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000029"""
Guido van Rossumc567c601992-11-05 22:22:37 +000030
Tim Peters88869f92001-01-14 23:36:06 +000031#
Guido van Rossum98d9fd32000-02-28 15:12:25 +000032# 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#
Guido van Rossumc567c601992-11-05 22:22:37 +000036
Guido van Rossum1115ab21992-11-04 15:51:30 +000037import os
38import sys
Guido van Rossum1115ab21992-11-04 15:51:30 +000039import string
40
Guido van Rossumb6775db1994-08-01 11:34:53 +000041# Import SOCKS module if it exists, else standard socket module socket
42try:
Tim Peters88869f92001-01-14 23:36:06 +000043 import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
44 from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
Guido van Rossumb6775db1994-08-01 11:34:53 +000045except ImportError:
Tim Peters88869f92001-01-14 23:36:06 +000046 import socket
Guido van Rossumb6775db1994-08-01 11:34:53 +000047
Skip Montanaroeccd02a2001-01-20 23:34:12 +000048__all__ = ["FTP","Netrc"]
Guido van Rossum1115ab21992-11-04 15:51:30 +000049
Guido van Rossumd3166071993-05-24 14:16:22 +000050# Magic number from <socket.h>
Tim Peters88869f92001-01-14 23:36:06 +000051MSG_OOB = 0x1 # Process data out of band
Guido van Rossumd3166071993-05-24 14:16:22 +000052
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
Tim Peters88869f92001-01-14 23:36:06 +000060class 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
Tim Peters88869f92001-01-14 23:36:06 +000078 '''An FTP client class.
Guido van Rossumd2560b01996-05-28 23:41:25 +000079
Tim Peters88869f92001-01-14 23:36:06 +000080 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.
Guido van Rossumd2560b01996-05-28 23:41:25 +000084
Tim Peters88869f92001-01-14 23:36:06 +000085 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.
Guido van Rossumd2560b01996-05-28 23:41:25 +000092'''
93
Fred Drake9c98a422001-02-28 21:46:37 +000094 debugging = 0
95 host = ''
96 port = FTP_PORT
97 sock = None
98 file = None
99 welcome = None
100 passiveserver = 1
101
Tim Peters88869f92001-01-14 23:36:06 +0000102 # Initialization method (called by class instantiation).
103 # Initialize host to localhost, port to standard ftp port
104 # Optional arguments are host (for connect()),
105 # and user, passwd, acct (for login())
Fred Drake9c98a422001-02-28 21:46:37 +0000106 def __init__(self, host='', user='', passwd='', acct=''):
Tim Peters88869f92001-01-14 23:36:06 +0000107 if host:
Fred Drake9c98a422001-02-28 21:46:37 +0000108 self.connect(host)
109 if user: self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000110
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000111 def connect(self, host = '', port = 0):
Martin v. Löwis4eb59402001-07-26 13:37:33 +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)'''
115 if host: self.host = host
116 if port: self.port = port
117 self.passiveserver = 0
Martin v. Löwis2ad25692001-07-31 08:40:21 +0000118 msg = "getaddrinfo returns an empty list"
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000119 for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
120 af, socktype, proto, canonname, sa = res
121 try:
122 self.sock = socket.socket(af, socktype, proto)
123 self.sock.connect(sa)
124 except socket.error, msg:
125 self.sock.close()
126 self.sock = None
127 continue
128 break
129 if not self.sock:
130 raise socket.error, msg
131 self.af = af
132 self.file = self.sock.makefile('rb')
133 self.welcome = self.getresp()
134 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000135
Tim Peters88869f92001-01-14 23:36:06 +0000136 def getwelcome(self):
137 '''Get the welcome message from the server.
138 (this is read and squirreled away by connect())'''
139 if self.debugging:
140 print '*welcome*', self.sanitize(self.welcome)
141 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000142
Tim Peters88869f92001-01-14 23:36:06 +0000143 def set_debuglevel(self, level):
144 '''Set the debugging level.
145 The required argument level means:
146 0: no debugging output (default)
147 1: print commands and responses but not body text etc.
148 2: also print raw lines read and sent before stripping CR/LF'''
149 self.debugging = level
150 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000151
Tim Peters88869f92001-01-14 23:36:06 +0000152 def set_pasv(self, val):
153 '''Use passive or active mode for data transfers.
154 With a false argument, use the normal PORT mode,
155 With a true argument, use the PASV command.'''
156 self.passiveserver = val
Guido van Rossumd2560b01996-05-28 23:41:25 +0000157
Tim Peters88869f92001-01-14 23:36:06 +0000158 # Internal: "sanitize" a string for printing
159 def sanitize(self, s):
160 if s[:5] == 'pass ' or s[:5] == 'PASS ':
161 i = len(s)
162 while i > 5 and s[i-1] in '\r\n':
163 i = i-1
164 s = s[:5] + '*'*(i-5) + s[i:]
165 return `s`
Guido van Rossumebaf1041995-05-05 15:54:14 +0000166
Tim Peters88869f92001-01-14 23:36:06 +0000167 # Internal: send one line to the server, appending CRLF
168 def putline(self, line):
169 line = line + CRLF
170 if self.debugging > 1: print '*put*', self.sanitize(line)
171 self.sock.send(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000172
Tim Peters88869f92001-01-14 23:36:06 +0000173 # Internal: send one command to the server (through putline())
174 def putcmd(self, line):
175 if self.debugging: print '*cmd*', self.sanitize(line)
176 self.putline(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000177
Tim Peters88869f92001-01-14 23:36:06 +0000178 # Internal: return one line from the server, stripping CRLF.
179 # Raise EOFError if the connection is closed
180 def getline(self):
181 line = self.file.readline()
182 if self.debugging > 1:
183 print '*get*', self.sanitize(line)
184 if not line: raise EOFError
185 if line[-2:] == CRLF: line = line[:-2]
186 elif line[-1:] in CRLF: line = line[:-1]
187 return line
Guido van Rossum1115ab21992-11-04 15:51:30 +0000188
Tim Peters88869f92001-01-14 23:36:06 +0000189 # Internal: get a response from the server, which may possibly
190 # consist of multiple lines. Return a single string with no
191 # trailing CRLF. If the response consists of multiple lines,
192 # these are separated by '\n' characters in the string
193 def getmultiline(self):
194 line = self.getline()
195 if line[3:4] == '-':
196 code = line[:3]
197 while 1:
198 nextline = self.getline()
199 line = line + ('\n' + nextline)
200 if nextline[:3] == code and \
201 nextline[3:4] != '-':
202 break
203 return line
Guido van Rossum1115ab21992-11-04 15:51:30 +0000204
Tim Peters88869f92001-01-14 23:36:06 +0000205 # Internal: get a response from the server.
206 # Raise various errors if the response indicates an error
207 def getresp(self):
208 resp = self.getmultiline()
209 if self.debugging: print '*resp*', self.sanitize(resp)
210 self.lastresp = resp[:3]
211 c = resp[:1]
212 if c == '4':
213 raise error_temp, resp
214 if c == '5':
215 raise error_perm, resp
216 if c not in '123':
217 raise error_proto, resp
218 return resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000219
Tim Peters88869f92001-01-14 23:36:06 +0000220 def voidresp(self):
221 """Expect a response beginning with '2'."""
222 resp = self.getresp()
223 if resp[0] != '2':
224 raise error_reply, resp
225 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000226
Tim Peters88869f92001-01-14 23:36:06 +0000227 def abort(self):
228 '''Abort a file transfer. Uses out-of-band data.
229 This does not follow the procedure from the RFC to send Telnet
230 IP and Synch; that doesn't seem to work with the servers I've
231 tried. Instead, just send the ABOR command as OOB data.'''
232 line = 'ABOR' + CRLF
233 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
234 self.sock.send(line, MSG_OOB)
235 resp = self.getmultiline()
236 if resp[:3] not in ('426', '226'):
237 raise error_proto, resp
Guido van Rossumd3166071993-05-24 14:16:22 +0000238
Tim Peters88869f92001-01-14 23:36:06 +0000239 def sendcmd(self, cmd):
240 '''Send a command and return the response.'''
241 self.putcmd(cmd)
242 return self.getresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000243
Tim Peters88869f92001-01-14 23:36:06 +0000244 def voidcmd(self, cmd):
245 """Send a command and expect a response beginning with '2'."""
246 self.putcmd(cmd)
247 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000248
Tim Peters88869f92001-01-14 23:36:06 +0000249 def sendport(self, host, port):
250 '''Send a PORT command with the current host and the given
251 port number.
252 '''
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000253 hbytes = host.split('.')
Tim Peters88869f92001-01-14 23:36:06 +0000254 pbytes = [`port/256`, `port%256`]
255 bytes = hbytes + pbytes
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000256 cmd = 'PORT ' + ','.join(bytes)
Tim Peters88869f92001-01-14 23:36:06 +0000257 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000258
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000259 def sendeprt(self, host, port):
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000260 '''Send a EPRT command with the current host and the given port number.'''
261 af = 0
262 if self.af == socket.AF_INET:
263 af = 1
264 if self.af == socket.AF_INET6:
265 af = 2
266 if af == 0:
267 raise error_proto, 'unsupported address family'
268 fields = ['', `af`, host, `port`, '']
269 cmd = 'EPRT ' + string.joinfields(fields, '|')
270 return self.voidcmd(cmd)
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000271
Tim Peters88869f92001-01-14 23:36:06 +0000272 def makeport(self):
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000273 '''Create a new socket and send a PORT command for it.'''
Martin v. Löwis2ad25692001-07-31 08:40:21 +0000274 msg = "getaddrinfo returns an empty list"
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000275 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 sock.close()
282 sock = None
283 continue
284 break
285 if not sock:
286 raise socket.error, msg
287 sock.listen(1)
288 port = sock.getsockname()[1] # Get proper port
289 host = self.sock.getsockname()[0] # Get proper host
290 if self.af == socket.AF_INET:
291 resp = self.sendport(host, port)
292 else:
293 resp = self.sendeprt(host, port)
294 return sock
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000295
296 def makepasv(self):
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000297 if self.af == socket.AF_INET:
298 host, port = parse227(self.sendcmd('PASV'))
299 else:
300 host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
301 return host, port
Guido van Rossum1115ab21992-11-04 15:51:30 +0000302
Tim Peters88869f92001-01-14 23:36:06 +0000303 def ntransfercmd(self, cmd, rest=None):
304 """Initiate a transfer over the data connection.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000305
Tim Peters88869f92001-01-14 23:36:06 +0000306 If the transfer is active, send a port command and the
307 transfer command, and accept the connection. If the server is
308 passive, send a pasv command, connect to it, and start the
309 transfer command. Either way, return the socket for the
310 connection and the expected size of the transfer. The
311 expected size may be None if it could not be determined.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000312
Tim Peters88869f92001-01-14 23:36:06 +0000313 Optional `rest' argument can be a string that is sent as the
314 argument to a RESTART command. This is essentially a server
315 marker used to tell the server to skip over any data up to the
316 given marker.
317 """
318 size = None
319 if self.passiveserver:
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000320 host, port = self.makepasv()
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000321 af, socktype, proto, canon, sa = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[0]
322 conn = socket.socket(af, socktype, proto)
323 conn.connect(sa)
Tim Peters88869f92001-01-14 23:36:06 +0000324 if rest is not None:
325 self.sendcmd("REST %s" % rest)
326 resp = self.sendcmd(cmd)
327 if resp[0] != '1':
328 raise error_reply, resp
329 else:
330 sock = self.makeport()
331 if rest is not None:
332 self.sendcmd("REST %s" % rest)
333 resp = self.sendcmd(cmd)
334 if resp[0] != '1':
335 raise error_reply, resp
336 conn, sockaddr = sock.accept()
337 if resp[:3] == '150':
338 # this is conditional in case we received a 125
339 size = parse150(resp)
340 return conn, size
Fred Drake4de02d91997-01-10 18:26:09 +0000341
Tim Peters88869f92001-01-14 23:36:06 +0000342 def transfercmd(self, cmd, rest=None):
343 """Like nstransfercmd() but returns only the socket."""
344 return self.ntransfercmd(cmd, rest)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000345
Tim Peters88869f92001-01-14 23:36:06 +0000346 def login(self, user = '', passwd = '', acct = ''):
347 '''Login, default anonymous.'''
348 if not user: user = 'anonymous'
349 if not passwd: passwd = ''
350 if not acct: acct = ''
351 if user == 'anonymous' and passwd in ('', '-'):
352 # get fully qualified domain name of local host
353 thishost = socket.getfqdn()
354 try:
355 if os.environ.has_key('LOGNAME'):
356 realuser = os.environ['LOGNAME']
357 elif os.environ.has_key('USER'):
358 realuser = os.environ['USER']
359 else:
360 realuser = 'anonymous'
361 except AttributeError:
362 # Not all systems have os.environ....
363 realuser = 'anonymous'
364 passwd = passwd + realuser + '@' + thishost
365 resp = self.sendcmd('USER ' + user)
366 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
367 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
368 if resp[0] != '2':
369 raise error_reply, resp
370 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000371
Tim Peters88869f92001-01-14 23:36:06 +0000372 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
373 """Retrieve data in binary mode.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000374
Tim Peters88869f92001-01-14 23:36:06 +0000375 `cmd' is a RETR command. `callback' is a callback function is
376 called for each block. No more than `blocksize' number of
377 bytes will be read from the socket. Optional `rest' is passed
378 to transfercmd().
Guido van Rossum1115ab21992-11-04 15:51:30 +0000379
Tim Peters88869f92001-01-14 23:36:06 +0000380 A new port is created for you. Return the response code.
381 """
382 self.voidcmd('TYPE I')
383 conn = self.transfercmd(cmd, rest)
384 while 1:
385 data = conn.recv(blocksize)
386 if not data:
387 break
388 callback(data)
389 conn.close()
390 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000391
Tim Peters88869f92001-01-14 23:36:06 +0000392 def retrlines(self, cmd, callback = None):
393 '''Retrieve data in line mode.
394 The argument is a RETR or LIST command.
395 The callback function (2nd argument) is called for each line,
396 with trailing CRLF stripped. This creates a new port for you.
397 print_line() is the default callback.'''
398 if not callback: callback = print_line
399 resp = self.sendcmd('TYPE A')
400 conn = self.transfercmd(cmd)
401 fp = conn.makefile('rb')
402 while 1:
403 line = fp.readline()
404 if self.debugging > 2: print '*retr*', `line`
405 if not line:
406 break
407 if line[-2:] == CRLF:
408 line = line[:-2]
409 elif line[-1:] == '\n':
410 line = line[:-1]
411 callback(line)
412 fp.close()
413 conn.close()
414 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000415
Guido van Rossum4ac83472001-02-15 13:50:36 +0000416 def storbinary(self, cmd, fp, blocksize=8192):
Tim Peters88869f92001-01-14 23:36:06 +0000417 '''Store a file in binary mode.'''
418 self.voidcmd('TYPE I')
419 conn = self.transfercmd(cmd)
420 while 1:
421 buf = fp.read(blocksize)
422 if not buf: break
423 conn.send(buf)
424 conn.close()
425 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000426
Tim Peters88869f92001-01-14 23:36:06 +0000427 def storlines(self, cmd, fp):
428 '''Store a file in line mode.'''
429 self.voidcmd('TYPE A')
430 conn = self.transfercmd(cmd)
431 while 1:
432 buf = fp.readline()
433 if not buf: break
434 if buf[-2:] != CRLF:
435 if buf[-1] in CRLF: buf = buf[:-1]
436 buf = buf + CRLF
437 conn.send(buf)
438 conn.close()
439 return self.voidresp()
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000440
Tim Peters88869f92001-01-14 23:36:06 +0000441 def acct(self, password):
442 '''Send new account name.'''
443 cmd = 'ACCT ' + password
444 return self.voidcmd(cmd)
Guido van Rossumc567c601992-11-05 22:22:37 +0000445
Tim Peters88869f92001-01-14 23:36:06 +0000446 def nlst(self, *args):
447 '''Return a list of files in a given directory (default the current).'''
448 cmd = 'NLST'
449 for arg in args:
450 cmd = cmd + (' ' + arg)
451 files = []
452 self.retrlines(cmd, files.append)
453 return files
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000454
Tim Peters88869f92001-01-14 23:36:06 +0000455 def dir(self, *args):
456 '''List a directory in long form.
457 By default list current directory to stdout.
458 Optional last argument is callback function; all
459 non-empty arguments before it are concatenated to the
460 LIST command. (This *should* only be used for a pathname.)'''
461 cmd = 'LIST'
462 func = None
463 if args[-1:] and type(args[-1]) != type(''):
464 args, func = args[:-1], args[-1]
465 for arg in args:
466 if arg:
467 cmd = cmd + (' ' + arg)
468 self.retrlines(cmd, func)
Guido van Rossumc567c601992-11-05 22:22:37 +0000469
Tim Peters88869f92001-01-14 23:36:06 +0000470 def rename(self, fromname, toname):
471 '''Rename a file.'''
472 resp = self.sendcmd('RNFR ' + fromname)
473 if resp[0] != '3':
474 raise error_reply, resp
475 return self.voidcmd('RNTO ' + toname)
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000476
Tim Peters88869f92001-01-14 23:36:06 +0000477 def delete(self, filename):
478 '''Delete a file.'''
479 resp = self.sendcmd('DELE ' + filename)
480 if resp[:3] in ('250', '200'):
481 return resp
482 elif resp[:1] == '5':
483 raise error_perm, resp
484 else:
485 raise error_reply, resp
Guido van Rossum02cf5821993-05-17 08:00:02 +0000486
Tim Peters88869f92001-01-14 23:36:06 +0000487 def cwd(self, dirname):
488 '''Change to a directory.'''
489 if dirname == '..':
490 try:
491 return self.voidcmd('CDUP')
492 except error_perm, msg:
493 if msg[:3] != '500':
494 raise error_perm, msg
495 elif dirname == '':
496 dirname = '.' # does nothing, but could return error
497 cmd = 'CWD ' + dirname
498 return self.voidcmd(cmd)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000499
Tim Peters88869f92001-01-14 23:36:06 +0000500 def size(self, filename):
501 '''Retrieve the size of a file.'''
502 # Note that the RFC doesn't say anything about 'SIZE'
503 resp = self.sendcmd('SIZE ' + filename)
504 if resp[:3] == '213':
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000505 return int(resp[3:].strip())
Guido van Rossumc567c601992-11-05 22:22:37 +0000506
Tim Peters88869f92001-01-14 23:36:06 +0000507 def mkd(self, dirname):
508 '''Make a directory, return its full pathname.'''
509 resp = self.sendcmd('MKD ' + dirname)
510 return parse257(resp)
Guido van Rossum98245091998-02-19 21:15:44 +0000511
Tim Peters88869f92001-01-14 23:36:06 +0000512 def rmd(self, dirname):
513 '''Remove a directory.'''
514 return self.voidcmd('RMD ' + dirname)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000515
Tim Peters88869f92001-01-14 23:36:06 +0000516 def pwd(self):
517 '''Return current working directory.'''
518 resp = self.sendcmd('PWD')
519 return parse257(resp)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000520
Tim Peters88869f92001-01-14 23:36:06 +0000521 def quit(self):
522 '''Quit, and close the connection.'''
523 resp = self.voidcmd('QUIT')
524 self.close()
525 return resp
526
527 def close(self):
528 '''Close the connection without assuming anything about it.'''
Fred Drake9c98a422001-02-28 21:46:37 +0000529 if self.file:
530 self.file.close()
531 self.sock.close()
532 self.file = self.sock = None
Guido van Rossumc567c601992-11-05 22:22:37 +0000533
534
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000535_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000536
537def parse150(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000538 '''Parse the '150' response for a RETR request.
539 Returns the expected transfer size or None; size is not guaranteed to
540 be present in the 150 message.
541 '''
542 if resp[:3] != '150':
543 raise error_reply, resp
544 global _150_re
545 if _150_re is None:
546 import re
547 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
548 m = _150_re.match(resp)
549 if m:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000550 return int(m.group(1))
Tim Peters88869f92001-01-14 23:36:06 +0000551 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000552
553
Guido van Rossumd2560b01996-05-28 23:41:25 +0000554def parse227(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000555 '''Parse the '227' response for a PASV request.
556 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
557 Return ('host.addr.as.numbers', port#) tuple.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000558
Tim Peters88869f92001-01-14 23:36:06 +0000559 if resp[:3] != '227':
560 raise error_reply, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000561 left = resp.find('(')
Tim Peters88869f92001-01-14 23:36:06 +0000562 if left < 0: raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000563 right = resp.find(')', left + 1)
Tim Peters88869f92001-01-14 23:36:06 +0000564 if right < 0:
565 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000566 numbers = resp[left+1:right].split(',')
Tim Peters88869f92001-01-14 23:36:06 +0000567 if len(numbers) != 6:
568 raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000569 host = '.'.join(numbers[:4])
570 port = (int(numbers[4]) << 8) + int(numbers[5])
Tim Peters88869f92001-01-14 23:36:06 +0000571 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000572
573
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000574def parse229(resp, peer):
575 '''Parse the '229' response for a EPSV request.
576 Raises error_proto if it does not contain '(|||port|)'
577 Return ('host.addr.as.numbers', port#) tuple.'''
578
579 if resp[:3] <> '229':
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000580 raise error_reply, resp
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000581 left = string.find(resp, '(')
582 if left < 0: raise error_proto, resp
583 right = string.find(resp, ')', left + 1)
584 if right < 0:
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000585 raise error_proto, resp # should contain '(|||port|)'
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000586 if resp[left + 1] <> resp[right - 1]:
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000587 raise error_proto, resp
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000588 parts = string.split(resp[left + 1:right], resp[left+1])
589 if len(parts) <> 5:
Martin v. Löwis4eb59402001-07-26 13:37:33 +0000590 raise error_proto, resp
Martin v. Löwisa43c2f82001-07-24 20:34:08 +0000591 host = peer[0]
592 port = string.atoi(parts[3])
593 return host, port
594
595
Guido van Rossumc567c601992-11-05 22:22:37 +0000596def parse257(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000597 '''Parse the '257' response for a MKD or PWD request.
598 This is a response to a MKD or PWD request: a directory name.
599 Returns the directoryname in the 257 reply.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000600
Tim Peters88869f92001-01-14 23:36:06 +0000601 if resp[:3] != '257':
602 raise error_reply, resp
603 if resp[3:5] != ' "':
604 return '' # Not compliant to RFC 959, but UNIX ftpd does this
605 dirname = ''
606 i = 5
607 n = len(resp)
608 while i < n:
609 c = resp[i]
610 i = i+1
611 if c == '"':
612 if i >= n or resp[i] != '"':
613 break
614 i = i+1
615 dirname = dirname + c
616 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000617
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000618
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000619def print_line(line):
Tim Peters88869f92001-01-14 23:36:06 +0000620 '''Default retrlines callback to print a line.'''
621 print line
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000622
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000623
Guido van Rossumd2560b01996-05-28 23:41:25 +0000624def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
Tim Peters88869f92001-01-14 23:36:06 +0000625 '''Copy file from one FTP-instance to another.'''
626 if not targetname: targetname = sourcename
627 type = 'TYPE ' + type
628 source.voidcmd(type)
629 target.voidcmd(type)
630 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
631 target.sendport(sourcehost, sourceport)
632 # RFC 959: the user must "listen" [...] BEFORE sending the
633 # transfer request.
634 # So: STOR before RETR, because here the target is a "user".
635 treply = target.sendcmd('STOR ' + targetname)
636 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
637 sreply = source.sendcmd('RETR ' + sourcename)
638 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
639 source.voidresp()
640 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000641
Tim Peters88869f92001-01-14 23:36:06 +0000642
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000643class Netrc:
Tim Peters88869f92001-01-14 23:36:06 +0000644 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000645
Tim Peters88869f92001-01-14 23:36:06 +0000646 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000647
Tim Peters88869f92001-01-14 23:36:06 +0000648 WARNING: This class is obsolete -- use module netrc instead.
Guido van Rossumc822a451998-12-22 16:49:16 +0000649
Tim Peters88869f92001-01-14 23:36:06 +0000650 """
651 __defuser = None
652 __defpasswd = None
653 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000654
Tim Peters88869f92001-01-14 23:36:06 +0000655 def __init__(self, filename=None):
656 if not filename:
657 if os.environ.has_key("HOME"):
658 filename = os.path.join(os.environ["HOME"],
659 ".netrc")
660 else:
661 raise IOError, \
662 "specify file to load or set $HOME"
663 self.__hosts = {}
664 self.__macros = {}
665 fp = open(filename, "r")
666 in_macro = 0
667 while 1:
668 line = fp.readline()
669 if not line: break
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000670 if in_macro and line.strip():
Tim Peters88869f92001-01-14 23:36:06 +0000671 macro_lines.append(line)
672 continue
673 elif in_macro:
674 self.__macros[macro_name] = tuple(macro_lines)
675 in_macro = 0
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000676 words = line.split()
Tim Peters88869f92001-01-14 23:36:06 +0000677 host = user = passwd = acct = None
678 default = 0
679 i = 0
680 while i < len(words):
681 w1 = words[i]
682 if i+1 < len(words):
683 w2 = words[i + 1]
684 else:
685 w2 = None
686 if w1 == 'default':
687 default = 1
688 elif w1 == 'machine' and w2:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000689 host = w2.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000690 i = i + 1
691 elif w1 == 'login' and w2:
692 user = w2
693 i = i + 1
694 elif w1 == 'password' and w2:
695 passwd = w2
696 i = i + 1
697 elif w1 == 'account' and w2:
698 acct = w2
699 i = i + 1
700 elif w1 == 'macdef' and w2:
701 macro_name = w2
702 macro_lines = []
703 in_macro = 1
704 break
705 i = i + 1
706 if default:
707 self.__defuser = user or self.__defuser
708 self.__defpasswd = passwd or self.__defpasswd
709 self.__defacct = acct or self.__defacct
710 if host:
711 if self.__hosts.has_key(host):
712 ouser, opasswd, oacct = \
713 self.__hosts[host]
714 user = user or ouser
715 passwd = passwd or opasswd
716 acct = acct or oacct
717 self.__hosts[host] = user, passwd, acct
718 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000719
Tim Peters88869f92001-01-14 23:36:06 +0000720 def get_hosts(self):
721 """Return a list of hosts mentioned in the .netrc file."""
722 return self.__hosts.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000723
Tim Peters88869f92001-01-14 23:36:06 +0000724 def get_account(self, host):
725 """Returns login information for the named host.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000726
Tim Peters88869f92001-01-14 23:36:06 +0000727 The return value is a triple containing userid,
728 password, and the accounting field.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000729
Tim Peters88869f92001-01-14 23:36:06 +0000730 """
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000731 host = host.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000732 user = passwd = acct = None
733 if self.__hosts.has_key(host):
734 user, passwd, acct = self.__hosts[host]
735 user = user or self.__defuser
736 passwd = passwd or self.__defpasswd
737 acct = acct or self.__defacct
738 return user, passwd, acct
Guido van Rossum8ca84201998-03-26 20:56:10 +0000739
Tim Peters88869f92001-01-14 23:36:06 +0000740 def get_macros(self):
741 """Return a list of all defined macro names."""
742 return self.__macros.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000743
Tim Peters88869f92001-01-14 23:36:06 +0000744 def get_macro(self, macro):
745 """Return a sequence of lines which define a named macro."""
746 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000747
Fred Drake475d51d1997-06-24 22:02:54 +0000748
Tim Peters88869f92001-01-14 23:36:06 +0000749
Guido van Rossum1115ab21992-11-04 15:51:30 +0000750def test():
Tim Peters88869f92001-01-14 23:36:06 +0000751 '''Test program.
752 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000753
Tim Peters88869f92001-01-14 23:36:06 +0000754 debugging = 0
755 rcfile = None
756 while sys.argv[1] == '-d':
757 debugging = debugging+1
758 del sys.argv[1]
759 if sys.argv[1][:2] == '-r':
760 # get name of alternate ~/.netrc file:
761 rcfile = sys.argv[1][2:]
762 del sys.argv[1]
763 host = sys.argv[1]
764 ftp = FTP(host)
765 ftp.set_debuglevel(debugging)
766 userid = passwd = acct = ''
767 try:
768 netrc = Netrc(rcfile)
769 except IOError:
770 if rcfile is not None:
771 sys.stderr.write("Could not open account file"
772 " -- using anonymous login.")
773 else:
774 try:
775 userid, passwd, acct = netrc.get_account(host)
776 except KeyError:
777 # no account for host
778 sys.stderr.write(
779 "No account -- using anonymous login.")
780 ftp.login(userid, passwd, acct)
781 for file in sys.argv[2:]:
782 if file[:2] == '-l':
783 ftp.dir(file[2:])
784 elif file[:2] == '-d':
785 cmd = 'CWD'
786 if file[2:]: cmd = cmd + ' ' + file[2:]
787 resp = ftp.sendcmd(cmd)
788 elif file == '-p':
789 ftp.set_pasv(not ftp.passiveserver)
790 else:
791 ftp.retrbinary('RETR ' + file, \
792 sys.stdout.write, 1024)
793 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000794
795
796if __name__ == '__main__':
Tim Peters88869f92001-01-14 23:36:06 +0000797 test()