blob: 429e834a934a10f0e7154dea6cb2b4a111b30528 [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.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000025>>>
26
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
Guido van Rossum98d9fd32000-02-28 15:12:25 +000031#
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#
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:
Peter Schneider-Kamp2d2785a2000-08-16 20:30:21 +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:
Guido van Rossum8ca84201998-03-26 20:56:10 +000046 import socket
Guido van Rossumb6775db1994-08-01 11:34:53 +000047
Guido van Rossum1115ab21992-11-04 15:51:30 +000048
Guido van Rossumd3166071993-05-24 14:16:22 +000049# Magic number from <socket.h>
50MSG_OOB = 0x1 # Process data out of band
51
52
Guido van Rossumc567c601992-11-05 22:22:37 +000053# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000054FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000055
56
Guido van Rossum21974791992-11-06 13:34:17 +000057# Exception raised when an error or invalid response is received
Fred Drake227b1202000-08-17 05:06:49 +000058class Error(Exception): pass
59class error_reply(Error): pass # unexpected [123]xx reply
60class error_temp(Error): pass # 4xx errors
61class error_perm(Error): pass # 5xx errors
62class error_proto(Error): pass # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000063
64
Guido van Rossum21974791992-11-06 13:34:17 +000065# All exceptions (hopefully) that may be raised here and that aren't
66# (always) programming errors on our side
Fred Drake227b1202000-08-17 05:06:49 +000067all_errors = (Error, socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000068
69
Guido van Rossum1115ab21992-11-04 15:51:30 +000070# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
71CRLF = '\r\n'
72
73
Guido van Rossum1115ab21992-11-04 15:51:30 +000074# The class itself
75class FTP:
76
Guido van Rossumd2560b01996-05-28 23:41:25 +000077 '''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 # Initialization method (called by class instantiation).
Guido van Rossum52fc1f61993-06-17 12:38:10 +000094 # Initialize host to localhost, port to standard ftp port
Guido van Rossumae3b3a31993-11-30 13:43:54 +000095 # Optional arguments are host (for connect()),
96 # and user, passwd, acct (for login())
Guido van Rossumb6775db1994-08-01 11:34:53 +000097 def __init__(self, host = '', user = '', passwd = '', acct = ''):
Guido van Rossum52fc1f61993-06-17 12:38:10 +000098 # Initialize the instance to something mostly harmless
Guido van Rossum1115ab21992-11-04 15:51:30 +000099 self.debugging = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000100 self.host = ''
101 self.port = FTP_PORT
102 self.sock = None
103 self.file = None
104 self.welcome = None
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000105 resp = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000106 if host:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000107 resp = self.connect(host)
108 if user: resp = self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000109
Guido van Rossumb6775db1994-08-01 11:34:53 +0000110 def connect(self, host = '', port = 0):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000111 '''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)'''
Guido van Rossumb6775db1994-08-01 11:34:53 +0000114 if host: self.host = host
115 if port: self.port = port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000116 self.passiveserver = 0
Guido van Rossum1115ab21992-11-04 15:51:30 +0000117 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000118 self.sock.connect((self.host, self.port))
Guido van Rossum24611f81996-09-30 22:02:50 +0000119 self.file = self.sock.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000120 self.welcome = self.getresp()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000121 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000122
Guido van Rossum1115ab21992-11-04 15:51:30 +0000123 def getwelcome(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000124 '''Get the welcome message from the server.
125 (this is read and squirreled away by connect())'''
Guido van Rossumebaf1041995-05-05 15:54:14 +0000126 if self.debugging:
127 print '*welcome*', self.sanitize(self.welcome)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000128 return self.welcome
129
Guido van Rossume65cce51993-11-08 15:05:21 +0000130 def set_debuglevel(self, level):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000131 '''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'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000136 self.debugging = level
Guido van Rossume65cce51993-11-08 15:05:21 +0000137 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000138
Guido van Rossumd2560b01996-05-28 23:41:25 +0000139 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
144
Guido van Rossumebaf1041995-05-05 15:54:14 +0000145 # Internal: "sanitize" a string for printing
146 def sanitize(self, s):
147 if s[:5] == 'pass ' or s[:5] == 'PASS ':
148 i = len(s)
149 while i > 5 and s[i-1] in '\r\n':
150 i = i-1
151 s = s[:5] + '*'*(i-5) + s[i:]
152 return `s`
153
Guido van Rossum1115ab21992-11-04 15:51:30 +0000154 # Internal: send one line to the server, appending CRLF
155 def putline(self, line):
156 line = line + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000157 if self.debugging > 1: print '*put*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000158 self.sock.send(line)
159
160 # Internal: send one command to the server (through putline())
161 def putcmd(self, line):
Guido van Rossumebaf1041995-05-05 15:54:14 +0000162 if self.debugging: print '*cmd*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000163 self.putline(line)
164
165 # Internal: return one line from the server, stripping CRLF.
166 # Raise EOFError if the connection is closed
167 def getline(self):
168 line = self.file.readline()
169 if self.debugging > 1:
Guido van Rossumebaf1041995-05-05 15:54:14 +0000170 print '*get*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000171 if not line: raise EOFError
172 if line[-2:] == CRLF: line = line[:-2]
173 elif line[-1:] in CRLF: line = line[:-1]
174 return line
175
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()
182 if line[3:4] == '-':
183 code = line[:3]
184 while 1:
185 nextline = self.getline()
186 line = line + ('\n' + nextline)
187 if nextline[:3] == code and \
188 nextline[3:4] <> '-':
189 break
190 return line
191
192 # Internal: get a response from the server.
193 # Raise various errors if the response indicates an error
194 def getresp(self):
195 resp = self.getmultiline()
Guido van Rossumebaf1041995-05-05 15:54:14 +0000196 if self.debugging: print '*resp*', self.sanitize(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000197 self.lastresp = resp[:3]
198 c = resp[:1]
199 if c == '4':
Guido van Rossumc567c601992-11-05 22:22:37 +0000200 raise error_temp, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000201 if c == '5':
Guido van Rossumc567c601992-11-05 22:22:37 +0000202 raise error_perm, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000203 if c not in '123':
Guido van Rossumc567c601992-11-05 22:22:37 +0000204 raise error_proto, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000205 return resp
206
Guido van Rossumc567c601992-11-05 22:22:37 +0000207 def voidresp(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000208 """Expect a response beginning with '2'."""
Guido van Rossumc567c601992-11-05 22:22:37 +0000209 resp = self.getresp()
210 if resp[0] <> '2':
211 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000212 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000213
Guido van Rossumd3166071993-05-24 14:16:22 +0000214 def abort(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000215 '''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.'''
Guido van Rossumd3166071993-05-24 14:16:22 +0000219 line = 'ABOR' + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000220 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
Guido van Rossumd3166071993-05-24 14:16:22 +0000221 self.sock.send(line, MSG_OOB)
222 resp = self.getmultiline()
223 if resp[:3] not in ('426', '226'):
224 raise error_proto, resp
225
Guido van Rossum1115ab21992-11-04 15:51:30 +0000226 def sendcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000227 '''Send a command and return the response.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000228 self.putcmd(cmd)
229 return self.getresp()
230
Guido van Rossumc567c601992-11-05 22:22:37 +0000231 def voidcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000232 """Send a command and expect a response beginning with '2'."""
Guido van Rossumc68a4011992-11-05 23:01:42 +0000233 self.putcmd(cmd)
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000234 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000235
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000236 def sendport(self, host, port):
Barry Warsaw100d81e2000-09-01 06:09:23 +0000237 '''Send a PORT command with the current host and the given
238 port number.
239 '''
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000240 hbytes = string.splitfields(host, '.')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000241 pbytes = [`port/256`, `port%256`]
242 bytes = hbytes + pbytes
243 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000244 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000245
Guido van Rossum1115ab21992-11-04 15:51:30 +0000246 def makeport(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000247 '''Create a new socket and send a PORT command for it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000248 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000249 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum303c1791995-06-20 17:21:42 +0000250 sock.bind(('', 0))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000251 sock.listen(1)
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000252 dummyhost, port = sock.getsockname() # Get proper port
253 host, dummyport = self.sock.getsockname() # Get proper host
254 resp = self.sendport(host, port)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000255 return sock
256
Barry Warsaw100d81e2000-09-01 06:09:23 +0000257 def ntransfercmd(self, cmd, rest=None):
258 """Initiate a transfer over the data connection.
259
260 If the transfer is active, send a port command and the
261 transfer command, and accept the connection. If the server is
262 passive, send a pasv command, connect to it, and start the
263 transfer command. Either way, return the socket for the
264 connection and the expected size of the transfer. The
265 expected size may be None if it could not be determined.
266
267 Optional `rest' argument can be a string that is sent as the
268 argument to a RESTART command. This is essentially a server
269 marker used to tell the server to skip over any data up to the
270 given marker.
271 """
Fred Drake4de02d91997-01-10 18:26:09 +0000272 size = None
Guido van Rossumd2560b01996-05-28 23:41:25 +0000273 if self.passiveserver:
274 host, port = parse227(self.sendcmd('PASV'))
Barry Warsaw100d81e2000-09-01 06:09:23 +0000275 conn=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000276 conn.connect((host, port))
Barry Warsaw100d81e2000-09-01 06:09:23 +0000277 if rest is not None:
278 self.sendcmd("REST %s" % rest)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000279 resp = self.sendcmd(cmd)
280 if resp[0] <> '1':
281 raise error_reply, resp
282 else:
283 sock = self.makeport()
Barry Warsaw100d81e2000-09-01 06:09:23 +0000284 if rest is not None:
285 self.sendcmd("REST %s" % rest)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000286 resp = self.sendcmd(cmd)
287 if resp[0] <> '1':
288 raise error_reply, resp
289 conn, sockaddr = sock.accept()
Fred Drake4de02d91997-01-10 18:26:09 +0000290 if resp[:3] == '150':
291 # this is conditional in case we received a 125
292 size = parse150(resp)
293 return conn, size
294
Barry Warsaw100d81e2000-09-01 06:09:23 +0000295 def transfercmd(self, cmd, rest=None):
296 """Like nstransfercmd() but returns only the socket."""
297 return self.ntransfercmd(cmd, rest)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000298
Guido van Rossumb6775db1994-08-01 11:34:53 +0000299 def login(self, user = '', passwd = '', acct = ''):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000300 '''Login, default anonymous.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000301 if not user: user = 'anonymous'
Guido van Rossum98245091998-02-19 21:15:44 +0000302 if not passwd: passwd = ''
303 if not acct: acct = ''
Guido van Rossumc567c601992-11-05 22:22:37 +0000304 if user == 'anonymous' and passwd in ('', '-'):
Peter Schneider-Kamp2d2785a2000-08-16 20:30:21 +0000305 # get fully qualified domain name of local host
306 thishost = socket.getfqdn()
Jack Jansen40b98351995-01-19 12:24:45 +0000307 try:
308 if os.environ.has_key('LOGNAME'):
309 realuser = os.environ['LOGNAME']
310 elif os.environ.has_key('USER'):
311 realuser = os.environ['USER']
312 else:
313 realuser = 'anonymous'
314 except AttributeError:
315 # Not all systems have os.environ....
Guido van Rossumc567c601992-11-05 22:22:37 +0000316 realuser = 'anonymous'
317 passwd = passwd + realuser + '@' + thishost
318 resp = self.sendcmd('USER ' + user)
319 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
320 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
321 if resp[0] <> '2':
322 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000323 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000324
Barry Warsaw100d81e2000-09-01 06:09:23 +0000325 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
326 """Retrieve data in binary mode.
327
328 `cmd' is a RETR command. `callback' is a callback function is
329 called for each block. No more than `blocksize' number of
330 bytes will be read from the socket. Optional `rest' is passed
331 to transfercmd().
332
333 A new port is created for you. Return the response code.
334 """
Guido van Rossumc567c601992-11-05 22:22:37 +0000335 self.voidcmd('TYPE I')
Barry Warsaw100d81e2000-09-01 06:09:23 +0000336 conn = self.transfercmd(cmd, rest)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000337 while 1:
338 data = conn.recv(blocksize)
339 if not data:
340 break
341 callback(data)
342 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000343 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000344
Guido van Rossumb6775db1994-08-01 11:34:53 +0000345 def retrlines(self, cmd, callback = None):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000346 '''Retrieve data in line mode.
347 The argument is a RETR or LIST command.
348 The callback function (2nd argument) is called for each line,
349 with trailing CRLF stripped. This creates a new port for you.
Fred Draked5f173b1999-07-07 13:36:59 +0000350 print_line() is the default callback.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000351 if not callback: callback = print_line
Guido van Rossumc567c601992-11-05 22:22:37 +0000352 resp = self.sendcmd('TYPE A')
353 conn = self.transfercmd(cmd)
Guido van Rossum24611f81996-09-30 22:02:50 +0000354 fp = conn.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000355 while 1:
356 line = fp.readline()
Guido van Rossumc0e68d11995-09-30 16:51:50 +0000357 if self.debugging > 2: print '*retr*', `line`
Guido van Rossum1115ab21992-11-04 15:51:30 +0000358 if not line:
359 break
360 if line[-2:] == CRLF:
361 line = line[:-2]
Guido van Rossumc6769c51998-12-21 16:26:31 +0000362 elif line[-1:] == '\n':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000363 line = line[:-1]
364 callback(line)
365 fp.close()
366 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000367 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000368
Guido van Rossumc567c601992-11-05 22:22:37 +0000369 def storbinary(self, cmd, fp, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000370 '''Store a file in binary mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000371 self.voidcmd('TYPE I')
372 conn = self.transfercmd(cmd)
373 while 1:
374 buf = fp.read(blocksize)
375 if not buf: break
376 conn.send(buf)
377 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000378 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000379
Guido van Rossumc567c601992-11-05 22:22:37 +0000380 def storlines(self, cmd, fp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000381 '''Store a file in line mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000382 self.voidcmd('TYPE A')
383 conn = self.transfercmd(cmd)
384 while 1:
385 buf = fp.readline()
386 if not buf: break
387 if buf[-2:] <> CRLF:
388 if buf[-1] in CRLF: buf = buf[:-1]
389 buf = buf + CRLF
390 conn.send(buf)
391 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000392 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000393
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000394 def acct(self, password):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000395 '''Send new account name.'''
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000396 cmd = 'ACCT ' + password
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000397 return self.voidcmd(cmd)
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000398
Guido van Rossumc567c601992-11-05 22:22:37 +0000399 def nlst(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000400 '''Return a list of files in a given directory (default the current).'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000401 cmd = 'NLST'
402 for arg in args:
403 cmd = cmd + (' ' + arg)
404 files = []
405 self.retrlines(cmd, files.append)
406 return files
407
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000408 def dir(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000409 '''List a directory in long form.
410 By default list current directory to stdout.
411 Optional last argument is callback function; all
412 non-empty arguments before it are concatenated to the
413 LIST command. (This *should* only be used for a pathname.)'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000414 cmd = 'LIST'
415 func = None
416 if args[-1:] and type(args[-1]) != type(''):
417 args, func = args[:-1], args[-1]
418 for arg in args:
419 if arg:
420 cmd = cmd + (' ' + arg)
421 self.retrlines(cmd, func)
422
Guido van Rossumc567c601992-11-05 22:22:37 +0000423 def rename(self, fromname, toname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000424 '''Rename a file.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000425 resp = self.sendcmd('RNFR ' + fromname)
426 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000427 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000428 return self.voidcmd('RNTO ' + toname)
Guido van Rossumc567c601992-11-05 22:22:37 +0000429
Guido van Rossum8ca84201998-03-26 20:56:10 +0000430 def delete(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000431 '''Delete a file.'''
Guido van Rossum8ca84201998-03-26 20:56:10 +0000432 resp = self.sendcmd('DELE ' + filename)
Guido van Rossum6bbd1d01998-07-02 20:41:20 +0000433 if resp[:3] in ('250', '200'):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000434 return resp
435 elif resp[:1] == '5':
436 raise error_perm, resp
437 else:
438 raise error_reply, resp
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000439
Guido van Rossum02cf5821993-05-17 08:00:02 +0000440 def cwd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000441 '''Change to a directory.'''
Guido van Rossumdf563861993-07-06 15:19:36 +0000442 if dirname == '..':
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000443 try:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000444 return self.voidcmd('CDUP')
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000445 except error_perm, msg:
446 if msg[:3] != '500':
447 raise error_perm, msg
Guido van Rossum1ebcf6a1999-08-18 21:51:10 +0000448 elif dirname == '':
449 dirname = '.' # does nothing, but could return error
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000450 cmd = 'CWD ' + dirname
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000451 return self.voidcmd(cmd)
Guido van Rossum02cf5821993-05-17 08:00:02 +0000452
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000453 def size(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000454 '''Retrieve the size of a file.'''
455 # Note that the RFC doesn't say anything about 'SIZE'
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000456 resp = self.sendcmd('SIZE ' + filename)
457 if resp[:3] == '213':
458 return string.atoi(string.strip(resp[3:]))
459
Guido van Rossumc567c601992-11-05 22:22:37 +0000460 def mkd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000461 '''Make a directory, return its full pathname.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000462 resp = self.sendcmd('MKD ' + dirname)
463 return parse257(resp)
464
Guido van Rossum98245091998-02-19 21:15:44 +0000465 def rmd(self, dirname):
466 '''Remove a directory.'''
467 return self.voidcmd('RMD ' + dirname)
468
Guido van Rossumc567c601992-11-05 22:22:37 +0000469 def pwd(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000470 '''Return current working directory.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000471 resp = self.sendcmd('PWD')
472 return parse257(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000473
Guido van Rossum1115ab21992-11-04 15:51:30 +0000474 def quit(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000475 '''Quit, and close the connection.'''
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000476 resp = self.voidcmd('QUIT')
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000477 self.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000478 return resp
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000479
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000480 def close(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000481 '''Close the connection without assuming anything about it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000482 self.file.close()
483 self.sock.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000484 del self.file, self.sock
485
486
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000487_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000488
489def parse150(resp):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000490 '''Parse the '150' response for a RETR request.
491 Returns the expected transfer size or None; size is not guaranteed to
492 be present in the 150 message.
493 '''
494 if resp[:3] != '150':
495 raise error_reply, resp
496 global _150_re
497 if _150_re is None:
498 import re
Fred Drake9291d271998-04-27 14:39:44 +0000499 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000500 m = _150_re.match(resp)
501 if m:
502 return string.atoi(m.group(1))
503 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000504
505
Guido van Rossumd2560b01996-05-28 23:41:25 +0000506def parse227(resp):
507 '''Parse the '227' response for a PASV request.
508 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
509 Return ('host.addr.as.numbers', port#) tuple.'''
510
511 if resp[:3] <> '227':
512 raise error_reply, resp
513 left = string.find(resp, '(')
514 if left < 0: raise error_proto, resp
515 right = string.find(resp, ')', left + 1)
516 if right < 0:
517 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
518 numbers = string.split(resp[left+1:right], ',')
519 if len(numbers) <> 6:
520 raise error_proto, resp
521 host = string.join(numbers[:4], '.')
522 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
523 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000524
525
Guido van Rossumc567c601992-11-05 22:22:37 +0000526def parse257(resp):
Guido van Rossum98245091998-02-19 21:15:44 +0000527 '''Parse the '257' response for a MKD or PWD request.
528 This is a response to a MKD or PWD request: a directory name.
Guido van Rossumd2560b01996-05-28 23:41:25 +0000529 Returns the directoryname in the 257 reply.'''
530
Guido van Rossumc567c601992-11-05 22:22:37 +0000531 if resp[:3] <> '257':
532 raise error_reply, resp
533 if resp[3:5] <> ' "':
534 return '' # Not compliant to RFC 959, but UNIX ftpd does this
535 dirname = ''
536 i = 5
537 n = len(resp)
538 while i < n:
539 c = resp[i]
540 i = i+1
541 if c == '"':
542 if i >= n or resp[i] <> '"':
543 break
544 i = i+1
545 dirname = dirname + c
546 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000547
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000548
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000549def print_line(line):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000550 '''Default retrlines callback to print a line.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000551 print line
552
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000553
Guido van Rossumd2560b01996-05-28 23:41:25 +0000554def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
555 '''Copy file from one FTP-instance to another.'''
556 if not targetname: targetname = sourcename
557 type = 'TYPE ' + type
558 source.voidcmd(type)
559 target.voidcmd(type)
560 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
561 target.sendport(sourcehost, sourceport)
562 # RFC 959: the user must "listen" [...] BEFORE sending the
563 # transfer request.
564 # So: STOR before RETR, because here the target is a "user".
565 treply = target.sendcmd('STOR ' + targetname)
566 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
567 sreply = source.sendcmd('RETR ' + sourcename)
568 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
569 source.voidresp()
570 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000571
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000572
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000573class Netrc:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000574 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000575
Guido van Rossum8ca84201998-03-26 20:56:10 +0000576 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000577
Guido van Rossumc822a451998-12-22 16:49:16 +0000578 WARNING: This class is obsolete -- use module netrc instead.
579
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000580 """
Guido van Rossum8ca84201998-03-26 20:56:10 +0000581 __defuser = None
582 __defpasswd = None
583 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000584
Guido van Rossum8ca84201998-03-26 20:56:10 +0000585 def __init__(self, filename=None):
586 if not filename:
587 if os.environ.has_key("HOME"):
588 filename = os.path.join(os.environ["HOME"],
589 ".netrc")
590 else:
591 raise IOError, \
592 "specify file to load or set $HOME"
593 self.__hosts = {}
594 self.__macros = {}
595 fp = open(filename, "r")
596 in_macro = 0
597 while 1:
598 line = fp.readline()
599 if not line: break
600 if in_macro and string.strip(line):
601 macro_lines.append(line)
602 continue
603 elif in_macro:
604 self.__macros[macro_name] = tuple(macro_lines)
605 in_macro = 0
606 words = string.split(line)
607 host = user = passwd = acct = None
608 default = 0
609 i = 0
610 while i < len(words):
611 w1 = words[i]
612 if i+1 < len(words):
613 w2 = words[i + 1]
614 else:
615 w2 = None
616 if w1 == 'default':
617 default = 1
618 elif w1 == 'machine' and w2:
619 host = string.lower(w2)
620 i = i + 1
621 elif w1 == 'login' and w2:
622 user = w2
623 i = i + 1
624 elif w1 == 'password' and w2:
625 passwd = w2
626 i = i + 1
627 elif w1 == 'account' and w2:
628 acct = w2
629 i = i + 1
630 elif w1 == 'macdef' and w2:
631 macro_name = w2
632 macro_lines = []
633 in_macro = 1
634 break
635 i = i + 1
636 if default:
637 self.__defuser = user or self.__defuser
638 self.__defpasswd = passwd or self.__defpasswd
639 self.__defacct = acct or self.__defacct
640 if host:
641 if self.__hosts.has_key(host):
642 ouser, opasswd, oacct = \
643 self.__hosts[host]
644 user = user or ouser
645 passwd = passwd or opasswd
646 acct = acct or oacct
647 self.__hosts[host] = user, passwd, acct
648 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000649
Guido van Rossum8ca84201998-03-26 20:56:10 +0000650 def get_hosts(self):
651 """Return a list of hosts mentioned in the .netrc file."""
652 return self.__hosts.keys()
653
654 def get_account(self, host):
655 """Returns login information for the named host.
656
657 The return value is a triple containing userid,
658 password, and the accounting field.
659
660 """
661 host = string.lower(host)
662 user = passwd = acct = None
663 if self.__hosts.has_key(host):
664 user, passwd, acct = self.__hosts[host]
665 user = user or self.__defuser
666 passwd = passwd or self.__defpasswd
667 acct = acct or self.__defacct
668 return user, passwd, acct
669
670 def get_macros(self):
671 """Return a list of all defined macro names."""
672 return self.__macros.keys()
673
674 def get_macro(self, macro):
675 """Return a sequence of lines which define a named macro."""
676 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000677
Fred Drake475d51d1997-06-24 22:02:54 +0000678
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000679
Guido van Rossum1115ab21992-11-04 15:51:30 +0000680def test():
Guido van Rossumd2560b01996-05-28 23:41:25 +0000681 '''Test program.
Fred Drake475d51d1997-06-24 22:02:54 +0000682 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000683
Guido van Rossumb6775db1994-08-01 11:34:53 +0000684 debugging = 0
Fred Drake475d51d1997-06-24 22:02:54 +0000685 rcfile = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000686 while sys.argv[1] == '-d':
687 debugging = debugging+1
688 del sys.argv[1]
Fred Drake475d51d1997-06-24 22:02:54 +0000689 if sys.argv[1][:2] == '-r':
690 # get name of alternate ~/.netrc file:
691 rcfile = sys.argv[1][2:]
692 del sys.argv[1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000693 host = sys.argv[1]
694 ftp = FTP(host)
695 ftp.set_debuglevel(debugging)
Fred Drake475d51d1997-06-24 22:02:54 +0000696 userid = passwd = acct = ''
697 try:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000698 netrc = Netrc(rcfile)
Fred Drake475d51d1997-06-24 22:02:54 +0000699 except IOError:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000700 if rcfile is not None:
701 sys.stderr.write("Could not open account file"
702 " -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000703 else:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000704 try:
705 userid, passwd, acct = netrc.get_account(host)
706 except KeyError:
707 # no account for host
708 sys.stderr.write(
709 "No account -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000710 ftp.login(userid, passwd, acct)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000711 for file in sys.argv[2:]:
712 if file[:2] == '-l':
713 ftp.dir(file[2:])
714 elif file[:2] == '-d':
715 cmd = 'CWD'
716 if file[2:]: cmd = cmd + ' ' + file[2:]
717 resp = ftp.sendcmd(cmd)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000718 elif file == '-p':
719 ftp.set_pasv(not ftp.passiveserver)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000720 else:
721 ftp.retrbinary('RETR ' + file, \
722 sys.stdout.write, 1024)
723 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000724
725
726if __name__ == '__main__':
727 test()