Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(224)

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/handlers.py

Issue 1154373005: Introduce WPTServe for running W3C Blink Layout tests (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Add executable bit to pass permchecks. Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 import cgi
2 import json
3 import os
4 import traceback
5 import urllib
6 import urlparse
7
8 from constants import content_types
9 from pipes import Pipeline, template
10 from ranges import RangeParser
11 from request import Authentication
12 from response import MultipartContent
13 from utils import HTTPException
14
15 __all__ = ["file_handler", "python_script_handler",
16 "FunctionHandler", "handler", "json_handler",
17 "as_is_handler", "ErrorHandler", "BasicAuthHandler"]
18
19
20 def guess_content_type(path):
21 ext = os.path.splitext(path)[1].lstrip(".")
22 if ext in content_types:
23 return content_types[ext]
24
25 return "application/octet-stream"
26
27
28
29 def filesystem_path(base_path, request, url_base="/"):
30 if base_path is None:
31 base_path = request.doc_root
32
33 path = request.url_parts.path
34
35 if path.startswith(url_base):
36 path = path[len(url_base):]
37
38 if ".." in path:
39 raise HTTPException(404)
40
41 new_path = os.path.join(base_path, path)
42
43 # Otherwise setting path to / allows access outside the root directory
44 if not new_path.startswith(base_path):
45 raise HTTPException(404)
46
47 return new_path
48
49 class DirectoryHandler(object):
50 def __init__(self, base_path=None, url_base="/"):
51 self.base_path = base_path
52 self.url_base = url_base
53
54 def __call__(self, request, response):
55 if not request.url_parts.path.endswith("/"):
56 raise HTTPException(404)
57
58 path = filesystem_path(self.base_path, request, self.url_base)
59
60 assert os.path.isdir(path)
61
62 response.headers = [("Content-Type", "text/html")]
63 response.content = """<!doctype html>
64 <meta name="viewport" content="width=device-width">
65 <title>Directory listing for %(path)s</title>
66 <h1>Directory listing for %(path)s</h1>
67 <ul>
68 %(items)s
69 </li>
70 """ % {"path": cgi.escape(request.url_parts.path),
71 "items": "\n".join(self.list_items(request, path))}
72
73 def list_items(self, request, path):
74 # TODO: this won't actually list all routes, only the
75 # ones that correspond to a real filesystem path. It's
76 # not possible to list every route that will match
77 # something, but it should be possible to at least list the
78 # statically defined ones
79 base_path = request.url_parts.path
80
81 if not base_path.endswith("/"):
82 base_path += "/"
83 if base_path != "/":
84 link = urlparse.urljoin(base_path, "..")
85 yield ("""<li class="dir"><a href="%(link)s">%(name)s</a>""" %
86 {"link": link, "name": ".."})
87 for item in sorted(os.listdir(path)):
88 link = cgi.escape(urllib.quote(item))
89 if os.path.isdir(os.path.join(path, item)):
90 link += "/"
91 class_ = "dir"
92 else:
93 class_ = "file"
94 yield ("""<li class="%(class)s"><a href="%(link)s">%(name)s</a>""" %
95 {"link": link, "name": cgi.escape(item), "class": class_})
96
97
98 directory_handler = DirectoryHandler()
99
100
101 class FileHandler(object):
102 def __init__(self, base_path=None, url_base="/"):
103 self.base_path = base_path
104 self.url_base = url_base
105 self.directory_handler = DirectoryHandler(self.base_path)
106
107 def __call__(self, request, response):
108 path = filesystem_path(self.base_path, request, self.url_base)
109
110 if os.path.isdir(path):
111 return self.directory_handler(request, response)
112 try:
113 #This is probably racy with some other process trying to change the file
114 file_size = os.stat(path).st_size
115 response.headers.update(self.get_headers(request, path))
116 if "Range" in request.headers:
117 try:
118 byte_ranges = RangeParser()(request.headers['Range'], file_s ize)
119 except HTTPException as e:
120 if e.code == 416:
121 response.headers.set("Content-Range", "bytes */%i" % fil e_size)
122 raise
123 else:
124 byte_ranges = None
125 data = self.get_data(response, path, byte_ranges)
126 response.content = data
127 query = urlparse.parse_qs(request.url_parts.query)
128
129 pipeline = None
130 if "pipe" in query:
131 pipeline = Pipeline(query["pipe"][-1])
132 elif os.path.splitext(path)[0].endswith(".sub"):
133 pipeline = Pipeline("sub")
134 if pipeline is not None:
135 response = pipeline(request, response)
136
137 return response
138
139 except (OSError, IOError):
140 raise HTTPException(404)
141
142 def get_headers(self, request, path):
143 rv = self.default_headers(path)
144 rv.extend(self.load_headers(request, os.path.join(os.path.split(path)[0] , "__dir__")))
145 rv.extend(self.load_headers(request, path))
146 return rv
147
148 def load_headers(self, request, path):
149 headers_path = path + ".sub.headers"
150 if os.path.exists(headers_path):
151 use_sub = True
152 else:
153 headers_path = path + ".headers"
154 use_sub = False
155
156 try:
157 with open(headers_path) as headers_file:
158 data = headers_file.read()
159 except IOError:
160 return []
161 else:
162 if use_sub:
163 data = template(request, data)
164 return [tuple(item.strip() for item in line.split(":", 1))
165 for line in data.splitlines() if line]
166
167 def get_data(self, response, path, byte_ranges):
168 with open(path, 'rb') as f:
169 if byte_ranges is None:
170 return f.read()
171 else:
172 response.status = 206
173 if len(byte_ranges) > 1:
174 parts_content_type, content = self.set_response_multipart(re sponse,
175 by te_ranges,
176 f)
177 for byte_range in byte_ranges:
178 content.append_part(self.get_range_data(f, byte_range),
179 parts_content_type,
180 [("Content-Range", byte_range.header _value())])
181 return content
182 else:
183 response.headers.set("Content-Range", byte_ranges[0].header_ value())
184 return self.get_range_data(f, byte_ranges[0])
185
186 def set_response_multipart(self, response, ranges, f):
187 parts_content_type = response.headers.get("Content-Type")
188 if parts_content_type:
189 parts_content_type = parts_content_type[-1]
190 else:
191 parts_content_type = None
192 content = MultipartContent()
193 response.headers.set("Content-Type", "multipart/byteranges; boundary=%s" % content.boundary)
194 return parts_content_type, content
195
196 def get_range_data(self, f, byte_range):
197 f.seek(byte_range.lower)
198 return f.read(byte_range.upper - byte_range.lower)
199
200 def default_headers(self, path):
201 return [("Content-Type", guess_content_type(path))]
202
203
204 file_handler = FileHandler()
205
206
207 class PythonScriptHandler(object):
208 def __init__(self, base_path=None, url_base="/"):
209 self.base_path = base_path
210 self.url_base = url_base
211
212 def __call__(self, request, response):
213 path = filesystem_path(self.base_path, request, self.url_base)
214
215 try:
216 environ = {"__file__": path}
217 execfile(path, environ, environ)
218 if "main" in environ:
219 handler = FunctionHandler(environ["main"])
220 handler(request, response)
221 else:
222 raise HTTPException(500, "No main function in script %s" % path)
223 except IOError:
224 raise HTTPException(404)
225
226 python_script_handler = PythonScriptHandler()
227
228 class FunctionHandler(object):
229 def __init__(self, func):
230 self.func = func
231
232 def __call__(self, request, response):
233 try:
234 rv = self.func(request, response)
235 except Exception:
236 msg = traceback.format_exc()
237 raise HTTPException(500, message=msg)
238 if rv is not None:
239 if isinstance(rv, tuple):
240 if len(rv) == 3:
241 status, headers, content = rv
242 response.status = status
243 elif len(rv) == 2:
244 headers, content = rv
245 else:
246 raise HTTPException(500)
247 response.headers.update(headers)
248 else:
249 content = rv
250 response.content = content
251
252
253 #The generic name here is so that this can be used as a decorator
254 def handler(func):
255 return FunctionHandler(func)
256
257
258 class JsonHandler(object):
259 def __init__(self, func):
260 self.func = func
261
262 def __call__(self, request, response):
263 return FunctionHandler(self.handle_request)(request, response)
264
265 def handle_request(self, request, response):
266 rv = self.func(request, response)
267 response.headers.set("Content-Type", "application/json")
268 enc = json.dumps
269 if isinstance(rv, tuple):
270 rv = list(rv)
271 value = tuple(rv[:-1] + [enc(rv[-1])])
272 length = len(value[-1])
273 else:
274 value = enc(rv)
275 length = len(value)
276 response.headers.set("Content-Length", length)
277 return value
278
279 def json_handler(func):
280 return JsonHandler(func)
281
282 class AsIsHandler(object):
283 def __init__(self, base_path=None, url_base="/"):
284 self.base_path = base_path
285 self.url_base = url_base
286
287 def __call__(self, request, response):
288 path = filesystem_path(self.base_path, request, self.url_base)
289
290 try:
291 with open(path) as f:
292 response.writer.write_content(f.read())
293 response.close_connection = True
294 except IOError:
295 raise HTTPException(404)
296
297 as_is_handler = AsIsHandler()
298
299 class BasicAuthHandler(object):
300 def __init__(self, handler, user, password):
301 """
302 A Basic Auth handler
303
304 :Args:
305 - handler: a secondary handler for the request after authentication is successful (example file_handler)
306 - user: string of the valid user name or None if any / all credentials are allowed
307 - password: string of the password required
308 """
309 self.user = user
310 self.password = password
311 self.handler = handler
312
313 def __call__(self, request, response):
314 if "authorization" not in request.headers:
315 response.status = 401
316 response.headers.set("WWW-Authenticate", "Basic")
317 return response
318 else:
319 auth = Authentication(request.headers)
320 if self.user is not None and (self.user != auth.username or self.pas sword != auth.password):
321 response.set_error(403, "Invalid username or password")
322 return response
323 return self.handler(request, response)
324
325 basic_auth_handler = BasicAuthHandler(file_handler, None, None)
326
327 class ErrorHandler(object):
328 def __init__(self, status):
329 self.status = status
330
331 def __call__(self, request, response):
332 response.set_error(self.status)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698