/Lib/SimpleHTTPServer.py
Python | 218 lines | 122 code | 17 blank | 79 comment | 19 complexity | a921195b9bfe76a4e71328cb2d1a0c3d MD5 | raw file
1"""Simple HTTP Server. 2 3This module builds on BaseHTTPServer by implementing the standard GET 4and HEAD requests in a fairly straightforward manner. 5 6""" 7 8 9__version__ = "0.6" 10 11__all__ = ["SimpleHTTPRequestHandler"] 12 13import os 14import posixpath 15import BaseHTTPServer 16import urllib 17import cgi 18import shutil 19import mimetypes 20try: 21 from cStringIO import StringIO 22except ImportError: 23 from StringIO import StringIO 24 25 26class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 27 28 """Simple HTTP request handler with GET and HEAD commands. 29 30 This serves files from the current directory and any of its 31 subdirectories. The MIME type for files is determined by 32 calling the .guess_type() method. 33 34 The GET and HEAD requests are identical except that the HEAD 35 request omits the actual contents of the file. 36 37 """ 38 39 server_version = "SimpleHTTP/" + __version__ 40 41 def do_GET(self): 42 """Serve a GET request.""" 43 f = self.send_head() 44 if f: 45 self.copyfile(f, self.wfile) 46 f.close() 47 48 def do_HEAD(self): 49 """Serve a HEAD request.""" 50 f = self.send_head() 51 if f: 52 f.close() 53 54 def send_head(self): 55 """Common code for GET and HEAD commands. 56 57 This sends the response code and MIME headers. 58 59 Return value is either a file object (which has to be copied 60 to the outputfile by the caller unless the command was HEAD, 61 and must be closed by the caller under all circumstances), or 62 None, in which case the caller has nothing further to do. 63 64 """ 65 path = self.translate_path(self.path) 66 f = None 67 if os.path.isdir(path): 68 if not self.path.endswith('/'): 69 # redirect browser - doing basically what apache does 70 self.send_response(301) 71 self.send_header("Location", self.path + "/") 72 self.end_headers() 73 return None 74 for index in "index.html", "index.htm": 75 index = os.path.join(path, index) 76 if os.path.exists(index): 77 path = index 78 break 79 else: 80 return self.list_directory(path) 81 ctype = self.guess_type(path) 82 try: 83 # Always read in binary mode. Opening files in text mode may cause 84 # newline translations, making the actual size of the content 85 # transmitted *less* than the content-length! 86 f = open(path, 'rb') 87 except IOError: 88 self.send_error(404, "File not found") 89 return None 90 self.send_response(200) 91 self.send_header("Content-type", ctype) 92 fs = os.fstat(f.fileno()) 93 self.send_header("Content-Length", str(fs[6])) 94 self.send_header("Last-Modified", self.date_time_string(fs.st_mtime)) 95 self.end_headers() 96 return f 97 98 def list_directory(self, path): 99 """Helper to produce a directory listing (absent index.html). 100 101 Return value is either a file object, or None (indicating an 102 error). In either case, the headers are sent, making the 103 interface the same as for send_head(). 104 105 """ 106 try: 107 list = os.listdir(path) 108 except os.error: 109 self.send_error(404, "No permission to list directory") 110 return None 111 list.sort(key=lambda a: a.lower()) 112 f = StringIO() 113 displaypath = cgi.escape(urllib.unquote(self.path)) 114 f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">') 115 f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath) 116 f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath) 117 f.write("<hr>\n<ul>\n") 118 for name in list: 119 fullname = os.path.join(path, name) 120 displayname = linkname = name 121 # Append / for directories or @ for symbolic links 122 if os.path.isdir(fullname): 123 displayname = name + "/" 124 linkname = name + "/" 125 if os.path.islink(fullname): 126 displayname = name + "@" 127 # Note: a link to a directory displays with @ and links with / 128 f.write('<li><a href="%s">%s</a>\n' 129 % (urllib.quote(linkname), cgi.escape(displayname))) 130 f.write("</ul>\n<hr>\n</body>\n</html>\n") 131 length = f.tell() 132 f.seek(0) 133 self.send_response(200) 134 self.send_header("Content-type", "text/html") 135 self.send_header("Content-Length", str(length)) 136 self.end_headers() 137 return f 138 139 def translate_path(self, path): 140 """Translate a /-separated PATH to the local filename syntax. 141 142 Components that mean special things to the local file system 143 (e.g. drive or directory names) are ignored. (XXX They should 144 probably be diagnosed.) 145 146 """ 147 # abandon query parameters 148 path = path.split('?',1)[0] 149 path = path.split('#',1)[0] 150 path = posixpath.normpath(urllib.unquote(path)) 151 words = path.split('/') 152 words = filter(None, words) 153 path = os.getcwd() 154 for word in words: 155 drive, word = os.path.splitdrive(word) 156 head, word = os.path.split(word) 157 if word in (os.curdir, os.pardir): continue 158 path = os.path.join(path, word) 159 return path 160 161 def copyfile(self, source, outputfile): 162 """Copy all data between two file objects. 163 164 The SOURCE argument is a file object open for reading 165 (or anything with a read() method) and the DESTINATION 166 argument is a file object open for writing (or 167 anything with a write() method). 168 169 The only reason for overriding this would be to change 170 the block size or perhaps to replace newlines by CRLF 171 -- note however that this the default server uses this 172 to copy binary data as well. 173 174 """ 175 shutil.copyfileobj(source, outputfile) 176 177 def guess_type(self, path): 178 """Guess the type of a file. 179 180 Argument is a PATH (a filename). 181 182 Return value is a string of the form type/subtype, 183 usable for a MIME Content-type header. 184 185 The default implementation looks the file's extension 186 up in the table self.extensions_map, using application/octet-stream 187 as a default; however it would be permissible (if 188 slow) to look inside the data to make a better guess. 189 190 """ 191 192 base, ext = posixpath.splitext(path) 193 if ext in self.extensions_map: 194 return self.extensions_map[ext] 195 ext = ext.lower() 196 if ext in self.extensions_map: 197 return self.extensions_map[ext] 198 else: 199 return self.extensions_map[''] 200 201 if not mimetypes.inited: 202 mimetypes.init() # try to read system mime.types 203 extensions_map = mimetypes.types_map.copy() 204 extensions_map.update({ 205 '': 'application/octet-stream', # Default 206 '.py': 'text/plain', 207 '.c': 'text/plain', 208 '.h': 'text/plain', 209 }) 210 211 212def test(HandlerClass = SimpleHTTPRequestHandler, 213 ServerClass = BaseHTTPServer.HTTPServer): 214 BaseHTTPServer.test(HandlerClass, ServerClass) 215 216 217if __name__ == '__main__': 218 test()