1 # An FTP client class. Based on RFC 959: File Transfer Protocol
2 # (FTP), by J. Postel and J. Reynolds
4 # Changes and improvements suggested by Steve Majewski
5 # Modified by Jack to work on the mac.
10 # >>> from ftplib import FTP
11 # >>> ftp = FTP('ftp.cwi.nl') # connect to host, default port
12 # >>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
13 # >>> ftp.retrlines('LIST') # list directory contents
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
21 # To download a file, use ftp.retrlines('RETR ' + filename),
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
33 # Import SOCKS module if it exists, else standard socket module socket
35 import SOCKS
; socket
= SOCKS
40 # Magic number from <socket.h>
41 MSG_OOB
= 0x1 # Process data out of band
44 # The standard FTP server control port
48 # Exception raised when an error or invalid response is received
49 error_reply
= 'ftplib.error_reply' # unexpected [123]xx reply
50 error_temp
= 'ftplib.error_temp' # 4xx errors
51 error_perm
= 'ftplib.error_perm' # 5xx errors
52 error_proto
= 'ftplib.error_proto' # response does not begin with [1-5]
55 # All exceptions (hopefully) that may be raised here and that aren't
56 # (always) programming errors on our side
57 all_errors
= (error_reply
, error_temp
, error_perm
, error_proto
, \
58 socket
.error
, IOError)
61 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
68 # New initialization method (called by class instantiation)
69 # Initialize host to localhost, port to standard ftp port
70 # Optional arguments are host (for connect()),
71 # and user, passwd, acct (for login())
72 def __init__(self
, host
= '', user
= '', passwd
= '', acct
= ''):
73 # Initialize the instance to something mostly harmless
82 if user
: self
.login(user
, passwd
, acct
)
84 # Connect to host. Arguments:
85 # - host: hostname to connect to (default previous host)
86 # - port: port to connect to (default previous port)
87 def connect(self
, host
= '', port
= 0):
88 if host
: self
.host
= host
89 if port
: self
.port
= port
90 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
91 self
.sock
.connect(self
.host
, self
.port
)
92 self
.file = self
.sock
.makefile('r')
93 self
.welcome
= self
.getresp()
95 # Get the welcome message from the server
96 # (this is read and squirreled away by connect())
98 if self
.debugging
: print '*welcome*', `self
.welcome`
101 # Set the debugging level. Argument level means:
102 # 0: no debugging output (default)
103 # 1: print commands and responses but not body text etc.
104 # 2: also print raw lines read and sent before stripping CR/LF
105 def set_debuglevel(self
, level
):
106 self
.debugging
= level
107 debug
= set_debuglevel
109 # Internal: send one line to the server, appending CRLF
110 def putline(self
, line
):
112 if self
.debugging
> 1: print '*put*', `line`
115 # Internal: send one command to the server (through putline())
116 def putcmd(self
, line
):
117 if self
.debugging
: print '*cmd*', `line`
120 # Internal: return one line from the server, stripping CRLF.
121 # Raise EOFError if the connection is closed
123 line
= self
.file.readline()
124 if self
.debugging
> 1:
125 print '*get*', `line`
126 if not line
: raise EOFError
127 if line
[-2:] == CRLF
: line
= line
[:-2]
128 elif line
[-1:] in CRLF
: line
= line
[:-1]
131 # Internal: get a response from the server, which may possibly
132 # consist of multiple lines. Return a single string with no
133 # trailing CRLF. If the response consists of multiple lines,
134 # these are separated by '\n' characters in the string
135 def getmultiline(self
):
136 line
= self
.getline()
140 nextline
= self
.getline()
141 line
= line
+ ('\n' + nextline
)
142 if nextline
[:3] == code
and \
143 nextline
[3:4] <> '-':
147 # Internal: get a response from the server.
148 # Raise various errors if the response indicates an error
150 resp
= self
.getmultiline()
151 if self
.debugging
: print '*resp*', `resp`
152 self
.lastresp
= resp
[:3]
155 raise error_temp
, resp
157 raise error_perm
, resp
159 raise error_proto
, resp
162 # Expect a response beginning with '2'
164 resp
= self
.getresp()
166 raise error_reply
, resp
168 # Abort a file transfer. Uses out-of-band data.
169 # This does not follow the procedure from the RFC to send Telnet
170 # IP and Synch; that doesn't seem to work with the servers I've
171 # tried. Instead, just send the ABOR command as OOB data.
174 if self
.debugging
> 1: print '*put urgent*', `line`
175 self
.sock
.send(line
, MSG_OOB
)
176 resp
= self
.getmultiline()
177 if resp
[:3] not in ('426', '226'):
178 raise error_proto
, resp
180 # Send a command and return the response
181 def sendcmd(self
, cmd
):
183 return self
.getresp()
185 # Send a command and expect a response beginning with '2'
186 def voidcmd(self
, cmd
):
190 # Send a PORT command with the current host and the given port number
191 def sendport(self
, port
):
192 hostname
= socket
.gethostname()
193 hostaddr
= socket
.gethostbyname(hostname
)
194 hbytes
= string
.splitfields(hostaddr
, '.')
195 pbytes
= [`port
/256`
, `port
%256`
]
196 bytes
= hbytes
+ pbytes
197 cmd
= 'PORT ' + string
.joinfields(bytes
, ',')
200 # Create a new socket and send a PORT command for it
203 sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
205 host
, port
= sock
.getsockname()
206 resp
= self
.sendport(port
)
209 # Send a port command and a transfer command, accept the connection
210 # and return the socket for the connection
211 def transfercmd(self
, cmd
):
212 sock
= self
.makeport()
213 resp
= self
.sendcmd(cmd
)
215 raise error_reply
, resp
216 conn
, sockaddr
= sock
.accept()
219 # Login, default anonymous
220 def login(self
, user
= '', passwd
= '', acct
= ''):
221 if not user
: user
= 'anonymous'
222 if user
== 'anonymous' and passwd
in ('', '-'):
223 thishost
= socket
.gethostname()
225 if os
.environ
.has_key('LOGNAME'):
226 realuser
= os
.environ
['LOGNAME']
227 elif os
.environ
.has_key('USER'):
228 realuser
= os
.environ
['USER']
230 realuser
= 'anonymous'
231 except AttributeError:
232 # Not all systems have os.environ....
233 realuser
= 'anonymous'
234 passwd
= passwd
+ realuser
+ '@' + thishost
235 resp
= self
.sendcmd('USER ' + user
)
236 if resp
[0] == '3': resp
= self
.sendcmd('PASS ' + passwd
)
237 if resp
[0] == '3': resp
= self
.sendcmd('ACCT ' + acct
)
239 raise error_reply
, resp
241 # Retrieve data in binary mode.
242 # The argument is a RETR command.
243 # The callback function is called for each block.
244 # This creates a new port for you
245 def retrbinary(self
, cmd
, callback
, blocksize
):
246 self
.voidcmd('TYPE I')
247 conn
= self
.transfercmd(cmd
)
249 data
= conn
.recv(blocksize
)
256 # Retrieve data in line mode.
257 # The argument is a RETR or LIST command.
258 # The callback function is called for each line, with trailing
259 # CRLF stripped. This creates a new port for you.
260 # print_lines is the default callback
261 def retrlines(self
, cmd
, callback
= None):
262 if not callback
: callback
= print_line
263 resp
= self
.sendcmd('TYPE A')
264 conn
= self
.transfercmd(cmd
)
265 fp
= conn
.makefile('r')
270 if line
[-2:] == CRLF
:
272 elif line
[:-1] == '\n':
279 # Store a file in binary mode
280 def storbinary(self
, cmd
, fp
, blocksize
):
281 self
.voidcmd('TYPE I')
282 conn
= self
.transfercmd(cmd
)
284 buf
= fp
.read(blocksize
)
290 # Store a file in line mode
291 def storlines(self
, cmd
, fp
):
292 self
.voidcmd('TYPE A')
293 conn
= self
.transfercmd(cmd
)
298 if buf
[-1] in CRLF
: buf
= buf
[:-1]
304 # Return a list of files in a given directory (default the current)
305 def nlst(self
, *args
):
308 cmd
= cmd
+ (' ' + arg
)
310 self
.retrlines(cmd
, files
.append
)
313 # List a directory in long form. By default list current directory
314 # to stdout. Optional last argument is callback function;
315 # all non-empty arguments before it are concatenated to the
316 # LIST command. (This *should* only be used for a pathname.)
317 def dir(self
, *args
):
320 if args
[-1:] and type(args
[-1]) != type(''):
321 args
, func
= args
[:-1], args
[-1]
324 cmd
= cmd
+ (' ' + arg
)
325 self
.retrlines(cmd
, func
)
328 def rename(self
, fromname
, toname
):
329 resp
= self
.sendcmd('RNFR ' + fromname
)
331 raise error_reply
, resp
332 self
.voidcmd('RNTO ' + toname
)
334 # Change to a directory
335 def cwd(self
, dirname
):
340 except error_perm
, msg
:
342 raise error_perm
, msg
343 cmd
= 'CWD ' + dirname
346 # Retrieve the size of a file
347 def size(self
, filename
):
348 resp
= self
.sendcmd('SIZE ' + filename
)
349 if resp
[:3] == '213':
350 return string
.atoi(string
.strip(resp
[3:]))
352 # Make a directory, return its full pathname
353 def mkd(self
, dirname
):
354 resp
= self
.sendcmd('MKD ' + dirname
)
355 return parse257(resp
)
357 # Return current wording directory
359 resp
= self
.sendcmd('PWD')
360 return parse257(resp
)
362 # Quit, and close the connection
367 # Close the connection without assuming anything about it
371 del self
.file, self
.sock
374 # Parse a response type 257
376 if resp
[:3] <> '257':
377 raise error_reply
, resp
378 if resp
[3:5] <> ' "':
379 return '' # Not compliant to RFC 959, but UNIX ftpd does this
387 if i
>= n
or resp
[i
] <> '"':
390 dirname
= dirname
+ c
393 # Default retrlines callback to print a line
394 def print_line(line
):
399 # Usage: ftp [-d] host [-l[dir]] [-d[dir]] [file] ...
403 while sys
.argv
[1] == '-d':
404 debugging
= debugging
+1
408 ftp
.set_debuglevel(debugging
)
410 for file in sys
.argv
[2:]:
413 elif file[:2] == '-d':
415 if file[2:]: cmd
= cmd
+ ' ' + file[2:]
416 resp
= ftp
.sendcmd(cmd
)
418 ftp
.retrbinary('RETR ' + file, \
419 sys
.stdout
.write
, 1024)