blob: 0ebefa87b3746fb3cab5e11b4c1e6f2782739238 [file] [log] [blame]
Guido van Rossumd2560b01996-05-28 23:41:25 +00001'''An FTP client class, and some helper functions.
2Based on RFC 959: File Transfer Protocol
3(FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00004
Guido van Rossumd2560b01996-05-28 23:41:25 +00005Changes and improvements suggested by Steve Majewski.
6Modified by Jack to work on the mac.
7Modified by Siebren to support docstrings and PASV.
Guido van Rossumae3b3a31993-11-30 13:43:54 +00008
Guido van Rossum1115ab21992-11-04 15:51:30 +00009
Guido van Rossumd2560b01996-05-28 23:41:25 +000010Example:
11
12>>> from ftplib import FTP
13>>> ftp = FTP('ftp.python.org') # connect to host, default port
14>>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
Guido van Rossum2f3941d1997-10-07 14:49:56 +000015'230 Guest login ok, access restrictions apply.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000016>>> ftp.retrlines('LIST') # list directory contents
17total 9
18drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
19drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
20drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
21drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
22d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
23drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
24drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
25drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
26-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
Guido van Rossum2f3941d1997-10-07 14:49:56 +000027'226 Transfer complete.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000028>>> ftp.quit()
Guido van Rossum2f3941d1997-10-07 14:49:56 +000029'221 Goodbye.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000030>>>
31
32A nice test that reveals some of the network dialogue would be:
33python ftplib.py -d localhost -l -p -l
34'''
Guido van Rossumc567c601992-11-05 22:22:37 +000035
36
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:
43 import SOCKS; socket = SOCKS
44except ImportError:
45 import socket
46
Guido van Rossum1115ab21992-11-04 15:51:30 +000047
Guido van Rossumd3166071993-05-24 14:16:22 +000048# Magic number from <socket.h>
49MSG_OOB = 0x1 # Process data out of band
50
51
Guido van Rossumc567c601992-11-05 22:22:37 +000052# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000053FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000054
55
Guido van Rossum21974791992-11-06 13:34:17 +000056# Exception raised when an error or invalid response is received
Guido van Rossumc567c601992-11-05 22:22:37 +000057error_reply = 'ftplib.error_reply' # unexpected [123]xx reply
58error_temp = 'ftplib.error_temp' # 4xx errors
59error_perm = 'ftplib.error_perm' # 5xx errors
60error_proto = 'ftplib.error_proto' # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000061
62
Guido van Rossum21974791992-11-06 13:34:17 +000063# All exceptions (hopefully) that may be raised here and that aren't
64# (always) programming errors on our side
65all_errors = (error_reply, error_temp, error_perm, error_proto, \
Guido van Rossumc0e68d11995-09-30 16:51:50 +000066 socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000067
68
Guido van Rossum1115ab21992-11-04 15:51:30 +000069# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
70CRLF = '\r\n'
71
72
Guido van Rossum1115ab21992-11-04 15:51:30 +000073# The class itself
74class FTP:
75
Guido van Rossumd2560b01996-05-28 23:41:25 +000076 '''An FTP client class.
77
78 To create a connection, call the class using these argument:
79 host, user, passwd, acct
80 These are all strings, and have default value ''.
81 Then use self.connect() with optional host and port argument.
82
83 To download a file, use ftp.retrlines('RETR ' + filename),
84 or ftp.retrbinary() with slightly different arguments.
85 To upload a file, use ftp.storlines() or ftp.storbinary(),
86 which have an open file as argument (see their definitions
87 below for details).
88 The download/upload functions first issue appropriate TYPE
89 and PORT or PASV commands.
90'''
91
92 # Initialization method (called by class instantiation).
Guido van Rossum52fc1f61993-06-17 12:38:10 +000093 # Initialize host to localhost, port to standard ftp port
Guido van Rossumae3b3a31993-11-30 13:43:54 +000094 # Optional arguments are host (for connect()),
95 # and user, passwd, acct (for login())
Guido van Rossumb6775db1994-08-01 11:34:53 +000096 def __init__(self, host = '', user = '', passwd = '', acct = ''):
Guido van Rossum52fc1f61993-06-17 12:38:10 +000097 # Initialize the instance to something mostly harmless
Guido van Rossum1115ab21992-11-04 15:51:30 +000098 self.debugging = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +000099 self.host = ''
100 self.port = FTP_PORT
101 self.sock = None
102 self.file = None
103 self.welcome = None
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000104 resp = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000105 if host:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000106 resp = self.connect(host)
107 if user: resp = self.login(user, passwd, acct)
108 return resp
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)
118 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):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000237 '''Send a PORT command with the current host and the given port number.'''
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000238 hbytes = string.splitfields(host, '.')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000239 pbytes = [`port/256`, `port%256`]
240 bytes = hbytes + pbytes
241 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000242 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000243
Guido van Rossum1115ab21992-11-04 15:51:30 +0000244 def makeport(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000245 '''Create a new socket and send a PORT command for it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000246 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000247 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum303c1791995-06-20 17:21:42 +0000248 sock.bind(('', 0))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000249 sock.listen(1)
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000250 dummyhost, port = sock.getsockname() # Get proper port
251 host, dummyport = self.sock.getsockname() # Get proper host
252 resp = self.sendport(host, port)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000253 return sock
254
Fred Drake4de02d91997-01-10 18:26:09 +0000255 def ntransfercmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000256 '''Initiate a transfer over the data connection.
257 If the transfer is active, send a port command and
258 the transfer command, and accept the connection.
259 If the server is passive, send a pasv command, connect
260 to it, and start the transfer command.
Fred Drake4de02d91997-01-10 18:26:09 +0000261 Either way, return the socket for the connection and
262 the expected size of the transfer. The expected size
263 may be None if it could not be determined.'''
264 size = None
Guido van Rossumd2560b01996-05-28 23:41:25 +0000265 if self.passiveserver:
266 host, port = parse227(self.sendcmd('PASV'))
267 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
268 conn.connect(host, port)
269 resp = self.sendcmd(cmd)
270 if resp[0] <> '1':
271 raise error_reply, resp
272 else:
273 sock = self.makeport()
274 resp = self.sendcmd(cmd)
275 if resp[0] <> '1':
276 raise error_reply, resp
277 conn, sockaddr = sock.accept()
Fred Drake4de02d91997-01-10 18:26:09 +0000278 if resp[:3] == '150':
279 # this is conditional in case we received a 125
280 size = parse150(resp)
281 return conn, size
282
283 def transfercmd(self, cmd):
284 '''Initiate a transfer over the data connection. Returns
285 the socket for the connection. See also ntransfercmd().'''
286 return self.ntransfercmd(cmd)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000287
Guido van Rossumb6775db1994-08-01 11:34:53 +0000288 def login(self, user = '', passwd = '', acct = ''):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000289 '''Login, default anonymous.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000290 if not user: user = 'anonymous'
291 if user == 'anonymous' and passwd in ('', '-'):
292 thishost = socket.gethostname()
Jack Jansen2db6bfc1995-05-04 15:02:18 +0000293 # Make sure it is fully qualified
294 if not '.' in thishost:
295 thisaddr = socket.gethostbyname(thishost)
Guido van Rossum303c1791995-06-20 17:21:42 +0000296 firstname, names, unused = \
297 socket.gethostbyaddr(thisaddr)
298 names.insert(0, firstname)
299 for name in names:
300 if '.' in name:
301 thishost = name
302 break
Jack Jansen40b98351995-01-19 12:24:45 +0000303 try:
304 if os.environ.has_key('LOGNAME'):
305 realuser = os.environ['LOGNAME']
306 elif os.environ.has_key('USER'):
307 realuser = os.environ['USER']
308 else:
309 realuser = 'anonymous'
310 except AttributeError:
311 # Not all systems have os.environ....
Guido van Rossumc567c601992-11-05 22:22:37 +0000312 realuser = 'anonymous'
313 passwd = passwd + realuser + '@' + thishost
314 resp = self.sendcmd('USER ' + user)
315 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
316 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
317 if resp[0] <> '2':
318 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000319 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000320
Guido van Rossumc567c601992-11-05 22:22:37 +0000321 def retrbinary(self, cmd, callback, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000322 '''Retrieve data in binary mode.
323 The argument is a RETR command.
324 The callback function is called for each block.
325 This creates a new port for you'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000326 self.voidcmd('TYPE I')
327 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000328 while 1:
329 data = conn.recv(blocksize)
330 if not data:
331 break
332 callback(data)
333 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000334 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000335
Guido van Rossumb6775db1994-08-01 11:34:53 +0000336 def retrlines(self, cmd, callback = None):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000337 '''Retrieve data in line mode.
338 The argument is a RETR or LIST command.
339 The callback function (2nd argument) is called for each line,
340 with trailing CRLF stripped. This creates a new port for you.
341 print_lines is the default callback.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000342 if not callback: callback = print_line
Guido van Rossumc567c601992-11-05 22:22:37 +0000343 resp = self.sendcmd('TYPE A')
344 conn = self.transfercmd(cmd)
Guido van Rossum24611f81996-09-30 22:02:50 +0000345 fp = conn.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000346 while 1:
347 line = fp.readline()
Guido van Rossumc0e68d11995-09-30 16:51:50 +0000348 if self.debugging > 2: print '*retr*', `line`
Guido van Rossum1115ab21992-11-04 15:51:30 +0000349 if not line:
350 break
351 if line[-2:] == CRLF:
352 line = line[:-2]
353 elif line[:-1] == '\n':
354 line = line[:-1]
355 callback(line)
356 fp.close()
357 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000358 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000359
Guido van Rossumc567c601992-11-05 22:22:37 +0000360 def storbinary(self, cmd, fp, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000361 '''Store a file in binary mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000362 self.voidcmd('TYPE I')
363 conn = self.transfercmd(cmd)
364 while 1:
365 buf = fp.read(blocksize)
366 if not buf: break
367 conn.send(buf)
368 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000369 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000370
Guido van Rossumc567c601992-11-05 22:22:37 +0000371 def storlines(self, cmd, fp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000372 '''Store a file in line mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000373 self.voidcmd('TYPE A')
374 conn = self.transfercmd(cmd)
375 while 1:
376 buf = fp.readline()
377 if not buf: break
378 if buf[-2:] <> CRLF:
379 if buf[-1] in CRLF: buf = buf[:-1]
380 buf = buf + CRLF
381 conn.send(buf)
382 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000383 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000384
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000385 def acct(self, password):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000386 '''Send new account name.'''
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000387 cmd = 'ACCT ' + password
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000388 return self.voidcmd(cmd)
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000389
Guido van Rossumc567c601992-11-05 22:22:37 +0000390 def nlst(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000391 '''Return a list of files in a given directory (default the current).'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000392 cmd = 'NLST'
393 for arg in args:
394 cmd = cmd + (' ' + arg)
395 files = []
396 self.retrlines(cmd, files.append)
397 return files
398
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000399 def dir(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000400 '''List a directory in long form.
401 By default list current directory to stdout.
402 Optional last argument is callback function; all
403 non-empty arguments before it are concatenated to the
404 LIST command. (This *should* only be used for a pathname.)'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000405 cmd = 'LIST'
406 func = None
407 if args[-1:] and type(args[-1]) != type(''):
408 args, func = args[:-1], args[-1]
409 for arg in args:
410 if arg:
411 cmd = cmd + (' ' + arg)
412 self.retrlines(cmd, func)
413
Guido van Rossumc567c601992-11-05 22:22:37 +0000414 def rename(self, fromname, toname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000415 '''Rename a file.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000416 resp = self.sendcmd('RNFR ' + fromname)
417 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000418 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000419 return self.voidcmd('RNTO ' + toname)
Guido van Rossumc567c601992-11-05 22:22:37 +0000420
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000421 def delete(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000422 '''Delete a file.'''
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000423 resp = self.sendcmd('DELE ' + filename)
424 if resp[:3] == '250':
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000425 return resp
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000426 elif resp[:1] == '5':
427 raise error_perm, resp
428 else:
429 raise error_reply, resp
430
Guido van Rossum02cf5821993-05-17 08:00:02 +0000431 def cwd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000432 '''Change to a directory.'''
Guido van Rossumdf563861993-07-06 15:19:36 +0000433 if dirname == '..':
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000434 try:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000435 return self.voidcmd('CDUP')
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000436 except error_perm, msg:
437 if msg[:3] != '500':
438 raise error_perm, msg
439 cmd = 'CWD ' + dirname
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000440 return self.voidcmd(cmd)
Guido van Rossum02cf5821993-05-17 08:00:02 +0000441
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000442 def size(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000443 '''Retrieve the size of a file.'''
444 # Note that the RFC doesn't say anything about 'SIZE'
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000445 resp = self.sendcmd('SIZE ' + filename)
446 if resp[:3] == '213':
447 return string.atoi(string.strip(resp[3:]))
448
Guido van Rossumc567c601992-11-05 22:22:37 +0000449 def mkd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000450 '''Make a directory, return its full pathname.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000451 resp = self.sendcmd('MKD ' + dirname)
452 return parse257(resp)
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
Fred Drake4de02d91997-01-10 18:26:09 +0000472import regex
473_150_re = regex.compile("150 .* (\([0-9][0-9]*\) bytes)", regex.casefold)
474
475def parse150(resp):
476 '''Parse the '150' response for a RETR request.
477 Returns the expected transfer size or None; size is not guaranteed to
478 be present in the 150 message.
479 '''
480 if resp[:3] != '150':
481 raise error_reply, resp
482 length = _150_re.match(resp)
483 if length >= 0:
484 return string.atoi(_150_re.group(1))
485 return None
486
487
Guido van Rossumd2560b01996-05-28 23:41:25 +0000488def parse227(resp):
489 '''Parse the '227' response for a PASV request.
490 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
491 Return ('host.addr.as.numbers', port#) tuple.'''
492
493 if resp[:3] <> '227':
494 raise error_reply, resp
495 left = string.find(resp, '(')
496 if left < 0: raise error_proto, resp
497 right = string.find(resp, ')', left + 1)
498 if right < 0:
499 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
500 numbers = string.split(resp[left+1:right], ',')
501 if len(numbers) <> 6:
502 raise error_proto, resp
503 host = string.join(numbers[:4], '.')
504 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
505 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000506
507
Guido van Rossumc567c601992-11-05 22:22:37 +0000508def parse257(resp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000509 '''Parse the '257' response for a MKD or RMD request.
510 This is a response to a MKD or RMD request: a directory name.
511 Returns the directoryname in the 257 reply.'''
512
Guido van Rossumc567c601992-11-05 22:22:37 +0000513 if resp[:3] <> '257':
514 raise error_reply, resp
515 if resp[3:5] <> ' "':
516 return '' # Not compliant to RFC 959, but UNIX ftpd does this
517 dirname = ''
518 i = 5
519 n = len(resp)
520 while i < n:
521 c = resp[i]
522 i = i+1
523 if c == '"':
524 if i >= n or resp[i] <> '"':
525 break
526 i = i+1
527 dirname = dirname + c
528 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000529
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000530
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000531def print_line(line):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000532 '''Default retrlines callback to print a line.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000533 print line
534
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000535
Guido van Rossumd2560b01996-05-28 23:41:25 +0000536def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
537 '''Copy file from one FTP-instance to another.'''
538 if not targetname: targetname = sourcename
539 type = 'TYPE ' + type
540 source.voidcmd(type)
541 target.voidcmd(type)
542 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
543 target.sendport(sourcehost, sourceport)
544 # RFC 959: the user must "listen" [...] BEFORE sending the
545 # transfer request.
546 # So: STOR before RETR, because here the target is a "user".
547 treply = target.sendcmd('STOR ' + targetname)
548 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
549 sreply = source.sendcmd('RETR ' + sourcename)
550 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
551 source.voidresp()
552 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000553
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000554
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000555class Netrc:
Fred Drake475d51d1997-06-24 22:02:54 +0000556 """Class to parse & provide access to 'netrc' format files.
557
558 See the netrc(4) man page for information on the file format.
559
560 """
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000561 __defuser = None
562 __defpasswd = None
563 __defacct = None
564
565 def __init__(self, filename=None):
566 if not filename:
567 if os.environ.has_key("HOME"):
568 filename = os.path.join(os.environ["HOME"], ".netrc")
569 else:
Fred Drake475d51d1997-06-24 22:02:54 +0000570 raise IOError, "specify file to load or set $HOME"
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000571 self.__hosts = {}
572 self.__macros = {}
573 fp = open(filename, "r")
574 in_macro = 0
575 while 1:
576 line = fp.readline()
577 if not line: break
578 if in_macro and string.strip(line):
579 macro_lines.append(line)
580 continue
581 elif in_macro:
582 self.__macros[macro_name] = tuple(macro_lines)
583 in_macro = 0
584 words = string.split(line)
585 host = user = passwd = acct = None
586 default = 0
587 i = 0
588 while i < len(words):
589 w1 = words[i]
590 if i+1 < len(words):
591 w2 = words[i + 1]
592 else:
593 w2 = None
594 if w1 == 'default':
595 default = 1
596 elif w1 == 'machine' and w2:
597 host = string.lower(w2)
598 i = i + 1
599 elif w1 == 'login' and w2:
600 user = w2
601 i = i + 1
602 elif w1 == 'password' and w2:
603 passwd = w2
604 i = i + 1
605 elif w1 == 'account' and w2:
606 acct = w2
607 i = i + 1
608 elif w1 == 'macdef' and w2:
609 macro_name = w2
610 macro_lines = []
611 in_macro = 1
612 break
613 i = i + 1
614 if default:
615 self.__defuser = user or self.__defuser
616 self.__defpasswd = passwd or self.__defpasswd
617 self.__defacct = acct or self.__defacct
618 if host:
619 if self.__hosts.has_key(host):
620 ouser, opasswd, oacct = self.__hosts[host]
621 user = user or ouser
622 passwd = passwd or opasswd
623 acct = acct or oacct
624 self.__hosts[host] = user, passwd, acct
625 fp.close()
626
627 def get_hosts(self):
628 """Return a list of hosts mentioned in the .netrc file."""
629 return self.__hosts.keys()
630
631 def get_account(self, host):
632 """Returns login information for the named host.
633
634 The return value is a triple containing userid, password, and the
635 accounting field.
636
637 """
638 host = string.lower(host)
Fred Drake475d51d1997-06-24 22:02:54 +0000639 user = passwd = acct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000640 if self.__hosts.has_key(host):
641 user, passwd, acct = self.__hosts[host]
642 user = user or self.__defuser
643 passwd = passwd or self.__defpasswd
644 acct = acct or self.__defacct
645 return user, passwd, acct
646
647 def get_macros(self):
648 """Return a list of all defined macro names."""
649 return self.__macros.keys()
650
651 def get_macro(self, macro):
652 """Return a sequence of lines which define a named macro."""
653 return self.__macros[macro]
654
Fred Drake475d51d1997-06-24 22:02:54 +0000655
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000656
Guido van Rossum1115ab21992-11-04 15:51:30 +0000657def test():
Guido van Rossumd2560b01996-05-28 23:41:25 +0000658 '''Test program.
Fred Drake475d51d1997-06-24 22:02:54 +0000659 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000660
Guido van Rossumb6775db1994-08-01 11:34:53 +0000661 debugging = 0
Fred Drake475d51d1997-06-24 22:02:54 +0000662 rcfile = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000663 while sys.argv[1] == '-d':
664 debugging = debugging+1
665 del sys.argv[1]
Fred Drake475d51d1997-06-24 22:02:54 +0000666 if sys.argv[1][:2] == '-r':
667 # get name of alternate ~/.netrc file:
668 rcfile = sys.argv[1][2:]
669 del sys.argv[1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000670 host = sys.argv[1]
671 ftp = FTP(host)
672 ftp.set_debuglevel(debugging)
Fred Drake475d51d1997-06-24 22:02:54 +0000673 userid = passwd = acct = ''
674 try:
675 netrc = Netrc(rcfile)
676 except IOError:
677 if rcfile is not None:
678 sys.stderr.write("Could not open account file"
679 " -- using anonymous login.")
680 else:
681 try:
682 userid, passwd, acct = netrc.get_account(host)
683 except KeyError:
684 # no account for host
685 sys.stderr.write("No account -- using anonymous login.")
686 ftp.login(userid, passwd, acct)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000687 for file in sys.argv[2:]:
688 if file[:2] == '-l':
689 ftp.dir(file[2:])
690 elif file[:2] == '-d':
691 cmd = 'CWD'
692 if file[2:]: cmd = cmd + ' ' + file[2:]
693 resp = ftp.sendcmd(cmd)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000694 elif file == '-p':
695 ftp.set_pasv(not ftp.passiveserver)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000696 else:
697 ftp.retrbinary('RETR ' + file, \
698 sys.stdout.write, 1024)
699 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000700
701
702if __name__ == '__main__':
703 test()