annotate fileserver/web.py @ 21:3ce9fe38f5e1

addendum
author Jeff Hammel <jhammel@mozilla.com>
date Wed, 29 Feb 2012 16:39:50 -0800
parents 1eb5e82605a5
children eb15c8321ad8
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
1 #!/usr/bin/env python
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
2
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
3 """
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
4 WSGI app for FileServer
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
5
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
6 Reference:
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
7 - http://docs.webob.org/en/latest/file-example.html
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
8 """
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
9
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
10 import mimetypes
1
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
11 import optparse
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
12 import os
1
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
13 import sys
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
14 from webob import Request, Response, exc
1
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
15 from wsgiref.simple_server import make_server
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
16
20
1eb5e82605a5 * flush out README
Jeff Hammel <jhammel@mozilla.com>
parents: 17
diff changeset
17 __all__ = ['get_mimetype', 'file_response', 'FileApp', 'DirectoryServer', 'main']
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
18
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
19 def get_mimetype(filename):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
20 type, encoding = mimetypes.guess_type(filename)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
21 # We'll ignore encoding, even though we shouldn't really
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
22 return type or 'application/octet-stream'
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
23
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
24 def file_response(filename):
20
1eb5e82605a5 * flush out README
Jeff Hammel <jhammel@mozilla.com>
parents: 17
diff changeset
25 """return a webob response object appropriate to a file name"""
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
26 res = Response(content_type=get_mimetype(filename))
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
27 res.body = open(filename, 'rb').read()
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
28 return res
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
29
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
30 class FileApp(object):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
31 """
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
32 serve static files
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
33 """
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
34
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
35 def __init__(self, filename):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
36 self.filename = filename
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
37
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
38 def __call__(self, environ, start_response):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
39 res = file_response(self.filename)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
40 return res(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
41
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
42 class DirectoryServer(object):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
43
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
44 def __init__(self, directory):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
45 assert os.path.exists(directory), "'%s' does not exist" % directory
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
46 assert os.path.isdir(directory), "'%s' is not a directory" % directory
2
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
47 self.directory = self.normpath(directory)
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
48
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
49 @staticmethod
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
50 def normpath(path):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
51 return os.path.normcase(os.path.abspath(path))
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
52
17
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
53 def check_path(self, path):
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
54 """
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
55 if under the root directory, returns the full path
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
56 otherwise, returns None
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
57 """
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
58 path = self.normpath(path)
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
59 if path == self.directory or path.startswith(self.directory + os.path.sep):
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
60 return path
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
61
2
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
62 def index(self, directory):
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
63 """
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
64 generate a directory listing for a given directory
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
65 """
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
66 parts = ['<html><head><title>Simple Index</title></head><body>']
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
67 listings = os.listdir(directory)
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
68 listings = [(os.path.isdir(os.path.join(directory, entry)) and entry + '/' or entry, entry)
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
69 for entry in listings]
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
70 for link, entry in listings:
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
71 parts.append('<a href="%s">%s</a><br/>' % (link, entry))
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
72 parts.append('</body></html>')
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
73 return '\n'.join(parts)
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
74
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
75 def __call__(self, environ, start_response):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
76 request = Request(environ)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
77 # TODO method_not_allowed: Allow: GET, HEAD
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
78 path_info = request.path_info
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
79 if not path_info:
13
e3993fa05b89 cleanup
Jeff Hammel <jhammel@mozilla.com>
parents: 12
diff changeset
80 response = exc.HTTPMovedPermanently(add_slash=True)
e3993fa05b89 cleanup
Jeff Hammel <jhammel@mozilla.com>
parents: 12
diff changeset
81 return response(environ, start_response)
17
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
82 full = self.check_path(os.path.join(self.directory, path_info.strip('/')))
2
8fb047af207a this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents: 1
diff changeset
83
17
27bd18f0a359 fix up security hole
Jeff Hammel <jhammel@mozilla.com>
parents: 13
diff changeset
84 if full is None:
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
85 # Out of bounds
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
86 return exc.HTTPNotFound()(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
87 if not os.path.exists(full):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
88 return exc.HTTPNotFound()(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
89
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
90 if os.path.isdir(full):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
91 # serve directory index
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
92 if not path_info.endswith('/'):
12
8127dde8da22 fix slashing
Jeff Hammel <jhammel@mozilla.com>
parents: 2
diff changeset
93 response = exc.HTTPMovedPermanently(add_slash=True)
8127dde8da22 fix slashing
Jeff Hammel <jhammel@mozilla.com>
parents: 2
diff changeset
94 return response(environ, start_response)
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
95 index = self.index(full)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
96 response = Response(index, content_type='text/html')
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
97 return response(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
98
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
99 # serve file
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
100 if path_info.endswith('/'):
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
101 # we create the `full` filename above by stripping off
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
102 # '/' from both sides; so we correct here
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
103 return exc.HTTPNotFound()(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
104 response = file_response(full)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
105 return response(environ, start_response)
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
106
1
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
107 def main(args=sys.argv[1:]):
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
108
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
109 # parse command line arguments
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
110 usage = '%prog [options] directory'
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
111 class PlainDescriptionFormatter(optparse.IndentedHelpFormatter):
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
112 """description formatter"""
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
113 def format_description(self, description):
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
114 if description:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
115 return description + '\n'
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
116 else:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
117 return ''
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
118 parser = optparse.OptionParser(usage=usage, description=__doc__, formatter=PlainDescriptionFormatter())
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
119 parser.add_option('-p', '--port', dest='port',
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
120 type='int', default=9999,
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
121 help='port [DEFAULT: %default]')
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
122 parser.add_option('-H', '--host', dest='host', default='0.0.0.0',
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
123 help='host [DEFAULT: %default]')
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
124 options, args = parser.parse_args(args)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
125
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
126 # get the directory
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
127 if not len(args) == 1:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
128 parser.print_help()
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
129 sys.exit(1)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
130 directory = args[0]
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
131 if not os.path.exists(directory):
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
132 parser.error("'%s' not found" % directory)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
133 if not os.path.isdir(directory):
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
134 parser.error("'%s' not a directory" % directory)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
135
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
136 # serve
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
137 app = DirectoryServer(directory)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
138 try:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
139 print 'http://%s:%s/' % (options.host, options.port)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
140 make_server(options.host, options.port, app).serve_forever()
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
141 except KeyboardInterrupt, ki:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
142 print "Cio, baby!"
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
143 except BaseException, e:
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
144 sys.exit("Problem initializing server: %s" % e)
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
145
0
8d31e36f084e initial stubbing
Jeff Hammel <jhammel@mozilla.com>
parents:
diff changeset
146 if __name__ == '__main__':
1
89d4f742ed1a include cli front end
Jeff Hammel <jhammel@mozilla.com>
parents: 0
diff changeset
147 main()