blob: ed588b5570ddabfcbfa9fc3765d6aa455363a7fc [file] [log] [blame]
Guido van Rossum1115ab21992-11-04 15:51:30 +00001# An FTP client class. Based on RFC 959: File Transfer Protocol
2# (FTP), by J. Postel and J. Reynolds
3
4
Guido van Rossumc567c601992-11-05 22:22:37 +00005# Example:
6#
7# >>> from ftplib import FTP
8# >>> ftp = FTP().init('ftp.cwi.nl') # connect to host, default port
9# >>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
10# >>> def handle_one_line(line): # callback for ftp.retrlines
11# ... print line
12# ...
13# >>> ftp.retrlines('LIST', handle_one_line) # list directory contents
14# total 43
15# d--x--x--x 2 root root 512 Jul 1 16:50 bin
16# d--x--x--x 2 root root 512 Sep 16 1991 etc
17# drwxr-xr-x 2 root ftp 10752 Sep 16 1991 lost+found
18# drwxr-srwt 15 root ftp 10240 Nov 5 20:43 pub
19# >>> ftp.quit()
20#
21# To download a file, use ftp.retrlines('RETR ' + filename, handle_one_line),
22# or ftp.retrbinary() with slightly different arguments.
23# To upload a file, use ftp.storlines() or ftp.storbinary(), which have
24# an open file as argument.
25# The download/upload functions first issue appropriate TYPE and PORT
26# commands.
27
28
Guido van Rossum1115ab21992-11-04 15:51:30 +000029import os
30import sys
31import socket
32import string
33
34
Guido van Rossumc567c601992-11-05 22:22:37 +000035# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000036FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000037
38
39# Exception raiseds when an error or invalid response is received
Guido van Rossumc567c601992-11-05 22:22:37 +000040error_reply = 'ftplib.error_reply' # unexpected [123]xx reply
41error_temp = 'ftplib.error_temp' # 4xx errors
42error_perm = 'ftplib.error_perm' # 5xx errors
43error_proto = 'ftplib.error_proto' # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000044
45
46# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
47CRLF = '\r\n'
48
49
50# Next port to be used by makeport(), with PORT_OFFSET added
Guido van Rossumc68a4011992-11-05 23:01:42 +000051# (This is now only used when the python interpreter doesn't support
52# the getsockname() method yet)
Guido van Rossum1115ab21992-11-04 15:51:30 +000053nextport = 0
54PORT_OFFSET = 40000
55PORT_CYCLE = 1000
Guido van Rossum1115ab21992-11-04 15:51:30 +000056
57
58# The class itself
59class FTP:
60
61 # Initialize an instance. Arguments:
62 # - host: hostname to connect to
63 # - port: port to connect to (default the standard FTP port)
64 def init(self, host, *args):
65 if len(args) > 1: raise TypeError, 'too many args'
66 if args: port = args[0]
67 else: port = FTP_PORT
68 self.host = host
69 self.port = port
70 self.debugging = 0
71 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
72 self.sock.connect(self.host, self.port)
73 self.file = self.sock.makefile('r')
74 self.welcome = self.getresp()
75 return self
76
77 # Get the welcome message from the server
78 # (this is read and squirreled away by init())
79 def getwelcome(self):
80 if self.debugging: print '*welcome*', `self.welcome`
81 return self.welcome
82
83 # Set the debugging level. Argument level means:
84 # 0: no debugging output (default)
85 # 1: print commands and responses but not body text etc.
86 # 2: also print raw lines read and sent before stripping CR/LF
87 def debug(self, level):
88 self.debugging = level
89
90 # Internal: send one line to the server, appending CRLF
91 def putline(self, line):
92 line = line + CRLF
93 if self.debugging > 1: print '*put*', `line`
94 self.sock.send(line)
95
96 # Internal: send one command to the server (through putline())
97 def putcmd(self, line):
98 if self.debugging: print '*cmd*', `line`
99 self.putline(line)
100
101 # Internal: return one line from the server, stripping CRLF.
102 # Raise EOFError if the connection is closed
103 def getline(self):
104 line = self.file.readline()
105 if self.debugging > 1:
106 print '*get*', `line`
107 if not line: raise EOFError
108 if line[-2:] == CRLF: line = line[:-2]
109 elif line[-1:] in CRLF: line = line[:-1]
110 return line
111
112 # Internal: get a response from the server, which may possibly
113 # consist of multiple lines. Return a single string with no
114 # trailing CRLF. If the response consists of multiple lines,
115 # these are separated by '\n' characters in the string
116 def getmultiline(self):
117 line = self.getline()
118 if line[3:4] == '-':
119 code = line[:3]
120 while 1:
121 nextline = self.getline()
122 line = line + ('\n' + nextline)
123 if nextline[:3] == code and \
124 nextline[3:4] <> '-':
125 break
126 return line
127
128 # Internal: get a response from the server.
129 # Raise various errors if the response indicates an error
130 def getresp(self):
131 resp = self.getmultiline()
132 if self.debugging: print '*resp*', `resp`
133 self.lastresp = resp[:3]
134 c = resp[:1]
135 if c == '4':
Guido van Rossumc567c601992-11-05 22:22:37 +0000136 raise error_temp, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000137 if c == '5':
Guido van Rossumc567c601992-11-05 22:22:37 +0000138 raise error_perm, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000139 if c not in '123':
Guido van Rossumc567c601992-11-05 22:22:37 +0000140 raise error_proto, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000141 return resp
142
Guido van Rossumc567c601992-11-05 22:22:37 +0000143 # Expect a response beginning with '2'
144 def voidresp(self):
145 resp = self.getresp()
146 if resp[0] <> '2':
147 raise error_reply, resp
148
Guido van Rossum1115ab21992-11-04 15:51:30 +0000149 # Send a command and return the response
150 def sendcmd(self, cmd):
151 self.putcmd(cmd)
152 return self.getresp()
153
Guido van Rossumc68a4011992-11-05 23:01:42 +0000154 # Send a command and expect a response beginning with '2'
Guido van Rossumc567c601992-11-05 22:22:37 +0000155 def voidcmd(self, cmd):
Guido van Rossumc68a4011992-11-05 23:01:42 +0000156 self.putcmd(cmd)
157 self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000158
Guido van Rossum1115ab21992-11-04 15:51:30 +0000159 # Send a PORT command with the current host and the given port number
160 def sendport(self, port):
161 hostname = socket.gethostname()
162 hostaddr = socket.gethostbyname(hostname)
163 hbytes = string.splitfields(hostaddr, '.')
164 pbytes = [`port/256`, `port%256`]
165 bytes = hbytes + pbytes
166 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossumc567c601992-11-05 22:22:37 +0000167 self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000168
169 # Create a new socket and send a PORT command for it
170 def makeport(self):
171 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000172 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossumc68a4011992-11-05 23:01:42 +0000173 try:
174 getsockname = sock.getsockname
175 except AttributeError:
176 if self.debugging > 1:
177 print '*** getsockname not supported',
178 print '-- using manual port assignment ***'
179 port = nextport + PORT_OFFSET
180 nextport = (nextport + 1) % PORT_CYCLE
181 sock.bind('', port)
182 getsockname = None
183 sock.listen(0) # Assigns the port if not explicitly bound
184 if getsockname:
185 host, port = getsockname()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000186 resp = self.sendport(port)
187 return sock
188
Guido van Rossumc567c601992-11-05 22:22:37 +0000189 # Send a port command and a transfer command, accept the connection
190 # and return the socket for the connection
191 def transfercmd(self, cmd):
Guido van Rossum1115ab21992-11-04 15:51:30 +0000192 sock = self.makeport()
193 resp = self.sendcmd(cmd)
194 if resp[0] <> '1':
195 raise error_reply, resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000196 conn, sockaddr = sock.accept()
197 return conn
198
199 # Login, default anonymous
200 def login(self, *args):
201 user = passwd = acct = ''
202 n = len(args)
203 if n > 3: raise TypeError, 'too many arguments'
204 if n > 0: user = args[0]
205 if n > 1: passwd = args[1]
206 if n > 2: acct = args[2]
207 if not user: user = 'anonymous'
208 if user == 'anonymous' and passwd in ('', '-'):
209 thishost = socket.gethostname()
210 if os.environ.has_key('LOGNAME'):
211 realuser = os.environ['LOGNAME']
212 elif os.environ.has_key('USER'):
213 realuser = os.environ['USER']
214 else:
215 realuser = 'anonymous'
216 passwd = passwd + realuser + '@' + thishost
217 resp = self.sendcmd('USER ' + user)
218 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
219 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
220 if resp[0] <> '2':
221 raise error_reply, resp
222
223 # Retrieve data in binary mode.
224 # The argument is a RETR command.
225 # The callback function is called for each block.
226 # This creates a new port for you
227 def retrbinary(self, cmd, callback, blocksize):
228 self.voidcmd('TYPE I')
229 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000230 while 1:
231 data = conn.recv(blocksize)
232 if not data:
233 break
234 callback(data)
235 conn.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000236 self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000237
Guido van Rossumc567c601992-11-05 22:22:37 +0000238 # Retrieve data in line mode.
Guido van Rossum1115ab21992-11-04 15:51:30 +0000239 # The argument is a RETR or LIST command.
240 # The callback function is called for each line, with trailing
241 # CRLF stripped. This creates a new port for you
242 def retrlines(self, cmd, callback):
Guido van Rossumc567c601992-11-05 22:22:37 +0000243 resp = self.sendcmd('TYPE A')
244 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000245 fp = conn.makefile('r')
246 while 1:
247 line = fp.readline()
248 if not line:
249 break
250 if line[-2:] == CRLF:
251 line = line[:-2]
252 elif line[:-1] == '\n':
253 line = line[:-1]
254 callback(line)
255 fp.close()
256 conn.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000257 self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000258
Guido van Rossumc567c601992-11-05 22:22:37 +0000259 # Store a file in binary mode
260 def storbinary(self, cmd, fp, blocksize):
261 self.voidcmd('TYPE I')
262 conn = self.transfercmd(cmd)
263 while 1:
264 buf = fp.read(blocksize)
265 if not buf: break
266 conn.send(buf)
267 conn.close()
268 self.voidresp()
269
270 # Store a file in line mode
271 def storlines(self, cmd, fp):
272 self.voidcmd('TYPE A')
273 conn = self.transfercmd(cmd)
274 while 1:
275 buf = fp.readline()
276 if not buf: break
277 if buf[-2:] <> CRLF:
278 if buf[-1] in CRLF: buf = buf[:-1]
279 buf = buf + CRLF
280 conn.send(buf)
281 conn.close()
282 self.voidresp()
283
284 # Return a list of files in a given directory (default the current)
285 def nlst(self, *args):
286 cmd = 'NLST'
287 for arg in args:
288 cmd = cmd + (' ' + arg)
289 files = []
290 self.retrlines(cmd, files.append)
291 return files
292
293 # Rename a file
294 def rename(self, fromname, toname):
295 resp = self.sendcmd('RNFR ' + fromname)
296 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000297 raise error_reply, resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000298 self.voidcmd('RNTO ' + toname)
299
300 # Make a directory, return its full pathname
301 def mkd(self, dirname):
302 resp = self.sendcmd('MKD ' + dirname)
303 return parse257(resp)
304
305 # Return current wording directory
306 def pwd(self):
307 resp = self.sendcmd('PWD')
308 return parse257(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000309
310 # Quit, and close the connection
311 def quit(self):
Guido van Rossumc567c601992-11-05 22:22:37 +0000312 self.voidcmd('QUIT')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000313 self.file.close()
314 self.sock.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000315 del self.file, self.sock
316
317
318# Parse a response type 257
319def parse257(resp):
320 if resp[:3] <> '257':
321 raise error_reply, resp
322 if resp[3:5] <> ' "':
323 return '' # Not compliant to RFC 959, but UNIX ftpd does this
324 dirname = ''
325 i = 5
326 n = len(resp)
327 while i < n:
328 c = resp[i]
329 i = i+1
330 if c == '"':
331 if i >= n or resp[i] <> '"':
332 break
333 i = i+1
334 dirname = dirname + c
335 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000336
337
338# Test program.
339# Usage: ftp [-d] host [-l[dir]] [-d[dir]] [file] ...
340def test():
341 import marshal
342 global nextport
343 try:
344 nextport = marshal.load(open('.@nextport', 'r'))
345 except IOError:
346 pass
347 try:
348 debugging = 0
349 while sys.argv[1] == '-d':
350 debugging = debugging+1
351 del sys.argv[1]
352 host = sys.argv[1]
353 ftp = FTP().init(host)
354 ftp.debug(debugging)
Guido van Rossumc567c601992-11-05 22:22:37 +0000355 ftp.login()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000356 def writeln(line): print line
357 for file in sys.argv[2:]:
358 if file[:2] == '-l':
359 cmd = 'LIST'
360 if file[2:]: cmd = cmd + ' ' + file[2:]
361 ftp.retrlines(cmd, writeln)
362 elif file[:2] == '-d':
363 cmd = 'CWD'
364 if file[2:]: cmd = cmd + ' ' + file[2:]
365 resp = ftp.sendcmd(cmd)
366 else:
367 ftp.retrbinary('RETR ' + file, \
368 sys.stdout.write, 1024)
369 ftp.quit()
370 finally:
371 marshal.dump(nextport, open('.@nextport', 'w'))