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

Side by Side Diff: Tools/Scripts/webkitpy/thirdparty/wpt/wpt/tools/wptserve/wptserve/request.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 base64
2 import cgi
3 import Cookie
4 import StringIO
5 import tempfile
6 import urlparse
7
8 import stash
9 from utils import HTTPException
10
11 missing = object()
12
13
14 class Server(object):
15 """Data about the server environment
16
17 .. attribute:: config
18
19 Environment configuration information with information about the
20 various servers running, their hostnames and ports.
21
22 .. attribute:: stash
23
24 Stash object holding state stored on the server between requests.
25
26 """
27 config = None
28
29 def __init__(self, request):
30 self.stash = stash.Stash(request.url_parts.path)
31
32
33 class InputFile(object):
34 max_buffer_size = 1024*1024
35
36 def __init__(self, rfile, length):
37 """File-like object used to provide a seekable view of request body data """
38 self._file = rfile
39 self.length = length
40
41 self._file_position = 0
42
43 if length > self.max_buffer_size:
44 self._buf = tempfile.TemporaryFile(mode="rw+b")
45 else:
46 self._buf = StringIO.StringIO()
47
48 @property
49 def _buf_position(self):
50 rv = self._buf.tell()
51 assert rv <= self._file_position
52 return rv
53
54 def read(self, bytes=-1):
55 assert self._buf_position <= self._file_position
56
57 if bytes < 0:
58 bytes = self.length - self._buf_position
59 bytes_remaining = min(bytes, self.length - self._buf_position)
60
61 if bytes_remaining == 0:
62 return ""
63
64 if self._buf_position != self._file_position:
65 buf_bytes = min(bytes_remaining, self._file_position - self._buf_pos ition)
66 old_data = self._buf.read(buf_bytes)
67 bytes_remaining -= buf_bytes
68 else:
69 old_data = ""
70
71 assert self._buf_position == self._file_position, (
72 "Before reading buffer position (%i) didn't match file position (%i) " %
73 (self._buf_position, self._file_position))
74 new_data = self._file.read(bytes_remaining)
75 self._buf.write(new_data)
76 self._file_position += bytes_remaining
77 assert self._buf_position == self._file_position, (
78 "After reading buffer position (%i) didn't match file position (%i)" %
79 (self._buf_position, self._file_position))
80
81 return old_data + new_data
82
83 def tell(self):
84 return self._buf_position
85
86 def seek(self, offset):
87 if offset > self.length or offset < 0:
88 raise ValueError
89 if offset <= self._file_position:
90 self._buf.seek(offset)
91 else:
92 self.read(offset - self._file_position)
93
94 def readline(self, max_bytes=None):
95 if max_bytes is None:
96 max_bytes = self.length - self._buf_position
97
98 if self._buf_position < self._file_position:
99 data = self._buf.readline(max_bytes)
100 if data.endswith("\n") or len(data) == max_bytes:
101 return data
102 else:
103 data = ""
104
105 assert self._buf_position == self._file_position
106
107 initial_position = self._file_position
108 found = False
109 buf = []
110 max_bytes -= len(data)
111 while not found:
112 readahead = self.read(min(2, max_bytes))
113 max_bytes -= len(readahead)
114 for i, c in enumerate(readahead):
115 if c == "\n":
116 buf.append(readahead[:i+1])
117 found = True
118 break
119 if not found:
120 buf.append(readahead)
121 if not readahead or not max_bytes:
122 break
123 new_data = "".join(buf)
124 data += new_data
125 self.seek(initial_position + len(new_data))
126 return data
127
128 def readlines(self):
129 rv = []
130 while True:
131 data = self.readline()
132 if data:
133 rv.append(data)
134 else:
135 break
136 return rv
137
138 def next(self):
139 data = self.readline()
140 if data:
141 return data
142 else:
143 raise StopIteration
144
145 def __iter__(self):
146 return self
147
148
149 class Request(object):
150 """Object representing a HTTP request.
151
152 .. attribute:: doc_root
153
154 The local directory to use as a base when resolving paths
155
156 .. attribute:: route_match
157
158 Regexp match object from matching the request path to the route
159 selected for the request.
160
161 .. attribute:: protocol_version
162
163 HTTP version specified in the request.
164
165 .. attribute:: method
166
167 HTTP method in the request.
168
169 .. attribute:: request_path
170
171 Request path as it appears in the HTTP request.
172
173 .. attribute:: url
174
175 Absolute URL for the request.
176
177 .. attribute:: headers
178
179 List of request headers.
180
181 .. attribute:: raw_input
182
183 File-like object representing the body of the request.
184
185 .. attribute:: url_parts
186
187 Parts of the requested URL as obtained by urlparse.urlsplit(path)
188
189 .. attribute:: request_line
190
191 Raw request line
192
193 .. attribute:: headers
194
195 RequestHeaders object providing a dictionary-like representation of
196 the request headers.
197
198 .. attribute:: body
199
200 Request body as a string
201
202 .. attribute:: GET
203
204 MultiDict representing the parameters supplied with the request.
205 Note that these may be present on non-GET requests; the name is
206 chosen to be familiar to users of other systems such as PHP.
207
208 .. attribute:: POST
209
210 MultiDict representing the request body parameters. Most parameters
211 are present as string values, but file uploads have file-like
212 values.
213
214 .. attribute:: cookies
215
216 Cookies object representing cookies sent with the request with a
217 dictionary-like interface.
218
219 .. attribute:: auth
220
221 Object with username and password properties representing any
222 credentials supplied using HTTP authentication.
223
224 .. attribute:: server
225
226 Server object containing information about the server environment.
227 """
228
229 def __init__(self, request_handler):
230 self.doc_root = request_handler.server.router.doc_root
231 self.route_match = None # Set by the router
232
233 self.protocol_version = request_handler.protocol_version
234 self.method = request_handler.command
235
236 scheme = request_handler.server.scheme
237 host = request_handler.headers.get("Host")
238 port = request_handler.server.server_address[1]
239
240 if host is None:
241 host = request_handler.server.server_address[0]
242 else:
243 if ":" in host:
244 host, port = host.split(":", 1)
245
246 self.request_path = request_handler.path
247
248 if self.request_path.startswith(scheme + "://"):
249 self.url = request_handler.path
250 else:
251 self.url = "%s://%s:%s%s" % (scheme,
252 host,
253 port,
254 self.request_path)
255 self.url_parts = urlparse.urlsplit(self.url)
256
257 self._raw_headers = request_handler.headers
258
259 self.request_line = request_handler.raw_requestline
260
261 self._headers = None
262
263 self.raw_input = InputFile(request_handler.rfile,
264 int(self.headers.get("Content-Length", 0)))
265 self._body = None
266
267 self._GET = None
268 self._POST = None
269 self._cookies = None
270 self._auth = None
271
272 self.server = Server(self)
273
274 def __repr__(self):
275 return "<Request %s %s>" % (self.method, self.url)
276
277 @property
278 def GET(self):
279 if self._GET is None:
280 params = urlparse.parse_qsl(self.url_parts.query, keep_blank_values= True)
281 self._GET = MultiDict()
282 for key, value in params:
283 self._GET.add(key, value)
284 return self._GET
285
286 @property
287 def POST(self):
288 if self._POST is None:
289 #Work out the post parameters
290 pos = self.raw_input.tell()
291 self.raw_input.seek(0)
292 fs = cgi.FieldStorage(fp=self.raw_input,
293 environ={"REQUEST_METHOD": self.method},
294 headers=self.headers,
295 keep_blank_values=True)
296 self._POST = MultiDict.from_field_storage(fs)
297 self.raw_input.seek(pos)
298 return self._POST
299
300 @property
301 def cookies(self):
302 if self._cookies is None:
303 parser = Cookie.BaseCookie()
304 cookie_headers = self.headers.get("cookie", "")
305 parser.load(cookie_headers)
306 cookies = Cookies()
307 for key, value in parser.iteritems():
308 cookies[key] = CookieValue(value)
309 self._cookies = cookies
310 return self._cookies
311
312 @property
313 def headers(self):
314 if self._headers is None:
315 self._headers = RequestHeaders(self._raw_headers)
316 return self._headers
317
318 @property
319 def body(self):
320 if self._body is None:
321 pos = self.raw_input.tell()
322 self.raw_input.seek(0)
323 self._body = self.raw_input.read()
324 self.raw_input.seek(pos)
325 return self._body
326
327 @property
328 def auth(self):
329 if self._auth is None:
330 self._auth = Authentication(self.headers)
331 return self._auth
332
333
334 class RequestHeaders(dict):
335 """Dictionary-like API for accessing request headers."""
336 def __init__(self, items):
337 for key, value in zip(items.keys(), items.values()):
338 key = key.lower()
339 if key in self:
340 self[key].append(value)
341 else:
342 dict.__setitem__(self, key, [value])
343
344 def __getitem__(self, key):
345 """Get all headers of a certain (case-insensitive) name. If there is
346 more than one, the values are returned comma separated"""
347 values = dict.__getitem__(self, key.lower())
348 if len(values) == 1:
349 return values[0]
350 else:
351 return ", ".join(values)
352
353 def __setitem__(self, name, value):
354 raise Exception
355
356 def get(self, key, default=None):
357 """Get a string representing all headers with a particular value,
358 with multiple headers separated by a comma. If no header is found
359 return a default value
360
361 :param key: The header name to look up (case-insensitive)
362 :param default: The value to return in the case of no match
363 """
364 try:
365 return self[key]
366 except KeyError:
367 return default
368
369 def get_list(self, key, default=missing):
370 """Get all the header values for a particular field name as
371 a list"""
372 try:
373 return dict.__getitem__(self, key.lower())
374 except KeyError:
375 if default is not missing:
376 return default
377 else:
378 raise
379
380 def __contains__(self, key):
381 return dict.__contains__(self, key.lower())
382
383 def iteritems(self):
384 for item in self:
385 yield item, self[item]
386
387 def itervalues(self):
388 for item in self:
389 yield self[item]
390
391 class CookieValue(object):
392 """Representation of cookies.
393
394 Note that cookies are considered read-only and the string value
395 of the cookie will not change if you update the field values.
396 However this is not enforced.
397
398 .. attribute:: key
399
400 The name of the cookie.
401
402 .. attribute:: value
403
404 The value of the cookie
405
406 .. attribute:: expires
407
408 The expiry date of the cookie
409
410 .. attribute:: path
411
412 The path of the cookie
413
414 .. attribute:: comment
415
416 The comment of the cookie.
417
418 .. attribute:: domain
419
420 The domain with which the cookie is associated
421
422 .. attribute:: max_age
423
424 The max-age value of the cookie.
425
426 .. attribute:: secure
427
428 Whether the cookie is marked as secure
429
430 .. attribute:: httponly
431
432 Whether the cookie is marked as httponly
433
434 """
435 def __init__(self, morsel):
436 self.key = morsel.key
437 self.value = morsel.value
438
439 for attr in ["expires", "path",
440 "comment", "domain", "max-age",
441 "secure", "version", "httponly"]:
442 setattr(self, attr.replace("-", "_"), morsel[attr])
443
444 self._str = morsel.OutputString()
445
446 def __str__(self):
447 return self._str
448
449 def __repr__(self):
450 return self._str
451
452 def __eq__(self, other):
453 """Equality comparison for cookies. Compares to other cookies
454 based on value alone and on non-cookies based on the equality
455 of self.value with the other object so that a cookie with value
456 "ham" compares equal to the string "ham"
457 """
458 if hasattr(other, "value"):
459 return self.value == other.value
460 return self.value == other
461
462
463 class MultiDict(dict):
464 """Dictionary type that holds multiple values for each
465 key"""
466 #TODO: this should perhaps also order the keys
467 def __init__(self):
468 pass
469
470 def __setitem__(self, name, value):
471 dict.__setitem__(self, name, [value])
472
473 def add(self, name, value):
474 if name in self:
475 dict.__getitem__(self, name).append(value)
476 else:
477 dict.__setitem__(self, name, [value])
478
479 def __getitem__(self, key):
480 """Get the first value with a given key"""
481 #TODO: should this instead be the last value?
482 return self.first(key)
483
484 def first(self, key, default=missing):
485 """Get the first value with a given key
486
487 :param key: The key to lookup
488 :param default: The default to return if key is
489 not found (throws if nothing is
490 specified)
491 """
492 if key in self and dict.__getitem__(self, key):
493 return dict.__getitem__(self, key)[0]
494 elif default is not missing:
495 return default
496 raise KeyError
497
498 def last(self, key, default=missing):
499 """Get the last value with a given key
500
501 :param key: The key to lookup
502 :param default: The default to return if key is
503 not found (throws if nothing is
504 specified)
505 """
506 if key in self and dict.__getitem__(self, key):
507 return dict.__getitem__(self, key)[-1]
508 elif default is not missing:
509 return default
510 raise KeyError
511
512 def get_list(self, key):
513 """Get all values with a given key as a list
514
515 :param key: The key to lookup
516 """
517 return dict.__getitem__(self, key)
518
519 @classmethod
520 def from_field_storage(cls, fs):
521 self = cls()
522 if fs.list is None:
523 return self
524 for key in fs:
525 values = fs[key]
526 if not isinstance(values, list):
527 values = [values]
528
529 for value in values:
530 if value.filename:
531 value = value
532 else:
533 value = value.value
534 self.add(key, value)
535 return self
536
537
538 class Cookies(MultiDict):
539 """MultiDict specialised for Cookie values"""
540 def __init__(self):
541 pass
542
543 def __getitem__(self, key):
544 return self.last(key)
545
546
547 class Authentication(object):
548 """Object for dealing with HTTP Authentication
549
550 .. attribute:: username
551
552 The username supplied in the HTTP Authorization
553 header, or None
554
555 .. attribute:: password
556
557 The password supplied in the HTTP Authorization
558 header, or None
559 """
560 def __init__(self, headers):
561 self.username = None
562 self.password = None
563
564 auth_schemes = {"Basic": self.decode_basic}
565
566 if "authorization" in headers:
567 header = headers.get("authorization")
568 auth_type, data = header.split(" ", 1)
569 if auth_type in auth_schemes:
570 self.username, self.password = auth_schemes[auth_type](data)
571 else:
572 raise HTTPException(400, "Unsupported authentication scheme %s" % auth_type)
573
574 def decode_basic(self, data):
575 decoded_data = base64.decodestring(data)
576 return decoded_data.split(":", 1)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698