1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17  r""" 
 18  HTTP Request State Machine 
 19  ========================== 
 20   
 21  This is a simple state pattern implementing the request flow. 
 22   
 23  :Variables: 
 24   - `CRLF`: ASCII CRLF sequence (\r\n) 
 25   
 26  :Types: 
 27   - `CRLF`: ``str`` 
 28  """ 
 29  __author__ = u"Andr\xe9 Malo" 
 30  __docformat__ = "restructuredtext en" 
 31   
 32  import re as _re 
 33  import socket as _socket 
 34   
 35  from wtf import Error 
 36  from wtf import stream as _stream 
 37  from wtf.impl import _util as _impl_util 
 38  from wtf.impl.http import _util as _http_util 
 39   
 40  CRLF = _http_util.CRLF 
 41   
 42  ParseError = _http_util.ParseError 
 43  BadRequest = _http_util.BadRequest 
 44   
 46      """ Request timed out """ 
 47      status = "408 Request Timeout" 
  48   
 50      """ Expectation failed """ 
 51      status = "417 Expectation Failed" 
  52   
 54      """ HTTP Version not supported """ 
 55      status = "505 HTTP Version Not Supported" 
  56   
 58      """ A feature is unimplemented """ 
 59      status = "501 Unimplemented" 
  60   
 62      """ Request line is invalid """ 
  63   
 64 -class InvalidContentLength(BadRequest): 
  65      """ The supplied content length is invalid """ 
  66   
 68      """ An invalid transfer encoding was supplied """ 
  69   
 71      """ Host header is mandatory with HTTP/1.1 """ 
  72   
 73   
 75      """ HTTP request state error """ 
  76   
 77   
 79      """ 
 80      Base state class 
 81   
 82      Every state method raises a StateError in here. Override implemented 
 83      function in derived classes. 
 84   
 85      :IVariables: 
 86       - `_request`: The request instance 
 87       - `response_started`: Was the response already started? 
 88   
 89      :Types: 
 90       - `_request`: `HTTPRequest` 
 91       - `response_started`: ``bool`` 
 92      """ 
 93      response_started = None 
 94   
 96          """ 
 97          Initialization 
 98   
 99          :Parameters: 
100           - `request`: Request instance 
101   
102          :Types: 
103           - `request`: `HTTPRequest` 
104          """ 
105          self._request = request 
 106   
108          """ 
109          (Re)set the request state 
110   
111          :Parameters: 
112           - `state`: New state class 
113   
114          :Types: 
115           - `state`: `BaseState` 
116          """ 
117          self._request.state = state(self._request) 
 118   
120          """ 
121          Read the request line, parse method, url and protocol version 
122   
123          :return: A tuple of method, url and protocol version 
124                   (``('method', 'url', (major, minor))``) 
125          :rtype: ``tuple`` 
126   
127          :Exceptions: 
128           - `InvalidRequestLine`: The request line was invalid 
129          """ 
130          raise StateError() 
 131   
133          """ 
134          Read and parse the headers 
135   
136          :return: A dict of comma folded headers, keys are lower cased 
137          :rtype: ``dict`` 
138   
139          :Exceptions: 
140           - `http._util.InvalidHeaderLine`: An invalid header line was found 
141           - `http._util.IncompleteHeaders`: The sents headers are incomplete 
142          """ 
143          raise StateError() 
 144   
146          """ 
147          Return a stream for the request body. 
148           
149          Chunking and Expect handling are done transparently. 
150   
151          :return: A stream for the request body 
152          :rtype: ``dict`` 
153          """ 
154          raise StateError() 
 155   
157          """ Send 100 Continue intermediate response """ 
158          raise StateError() 
 159   
167   
169          """ 
170          Send the response status line 
171   
172          :Parameters: 
173           - `status`: The status line (3 digit code, space, reason) 
174   
175          :Types: 
176           - `status`: ``str`` 
177          """ 
178           
179   
180          raise StateError() 
 181   
183          """ 
184          Send the headers 
185   
186          Actually the headers may be accumulated until finish_headers is called 
187   
188          :Parameters: 
189           - `headers`: List of headers (``[('name', 'value'), ...]``) 
190   
191          :Types: 
192           - `headers`: ``iterable`` 
193          """ 
194           
195   
196          raise StateError() 
 197   
199          """ 
200          Finish header sending, prepare the response for the body 
201   
202          This function does *not* guarantee, that headers are actually sent. 
203          It might be implemented in a manner that headers are still being 
204          modified, when the first body chunk comes in (but they all must 
205          be flushed then). 
206          """ 
207          raise StateError() 
 208   
210          """ Retrieve the response body stream """ 
211          raise StateError() 
  212   
213   
215      """ 
216      Initial state of a request, for example on a fresh connection. 
217   
218      States to go from here: 
219   
220      - `RequestLineReadyState` 
221   
222      :CVariables: 
223       - `_LINE_MATCH`: Regex match callable to check if the line does not  
224         start with a WS 
225       - `_VER_MATCH`: Regex match callable to parse the HTTP version 
226   
227      :Types: 
228       - `_LINE_MATCH`: ``callable`` 
229       - `_VER_MATCH`: ``callable`` 
230      """ 
231      response_started = False 
232      _LINE_MATCH = _re.compile(r'\S').match 
233      _VER_MATCH = _re.compile(r'HTTP/(?P<major>\d+)\.(?P<minor>\d+)$').match 
234   
 262   
263   
265      """ 
266      The headers can be read now 
267   
268      States to go from here: 
269   
270      - `RequestHeadersReadyState` 
271      """ 
272      response_started = False 
273   
 287   
288   
290      """ 
291      The body can be read now and/or the response can be started 
292   
293      States to go from here: 
294   
295      - `ResponseContinueWaitState` 
296      - `ResponseStatusWaitState` 
297   
298      :CVariables: 
299       - `_DECODERS`: Decoder mapping accessor 
300   
301      :Types: 
302       - `_DECODERS`: ``callable`` 
303      """ 
304      response_started = False 
305      _DECODERS = { 
306          'chunked': _http_util.ChunkedReader, 
307      }.get 
308   
314   
316          """ Return a body stream """ 
317           
318   
319          next_state, request = ResponseStatusWaitState, self._request 
320          stream = oldstream = request.connection.reader 
321   
322           
323          if request.protocol >= (1, 1): 
324              codings = [item for item in [ 
325                  item.strip().lower() for item in 
326                  request.headers.get('transfer-encoding', '').split(',') 
327              ] if item and item != 'identity'][::-1] 
328              if codings: 
329                  if codings[0] != 'chunked': 
330                      raise InvalidTransferEncoding( 
331                          "Last transfer encoding MUST be chunked" 
332                      ) 
333                  for coding in codings: 
334                      decoder = self._DECODERS(coding) 
335                      if decoder is None: 
336                          raise UnImplemented( 
337                              "Transfer-Encoding: %s in not implemented" % 
338                              coding 
339                          ) 
340                      stream = decoder(stream) 
341   
342           
343          if stream == oldstream and 'content-length' in request.headers: 
344              try: 
345                  clen = int(request.headers['content-length']) 
346                  if clen < 0: 
347                      raise ValueError() 
348              except (TypeError, ValueError): 
349                  raise InvalidContentLength( 
350                      "Provide a valid Content-Length, please." 
351                  ) 
352              else: 
353                  stream = _impl_util.ContentLengthReader(stream, clen) 
354   
355           
356          if stream == oldstream: 
357              stream = None 
358   
359           
360          elif request.protocol >= (1, 1) and 'expect' in request.headers: 
361               
362               
363               
364              expectations = set([item.strip().lower() for item in 
365                  request.headers['expect'].split(',')]) 
366              if '100-continue' in expectations and len(expectations) == 1: 
367                  stream = _http_util.ExpectationReader(stream, request) 
368              elif expectations: 
369                  raise ExpectationFailed("Unrecognized expectation") 
370              next_state = ResponseContinueWaitState 
371              request.expects_100 = True 
372   
373          if stream is not None and stream != oldstream: 
374              stream = _stream.GenericStream(stream, read_exact=True) 
375   
376          return stream, next_state 
 377   
379          """ Determine the stream for the request body """ 
380          self._set_state(self._next_state) 
381          return self._request._request_body_stream  
 382   
 387   
388   
390      """ 
391      We're waiting for either 100 continue emission of send_status 
392   
393      States to go from here: 
394   
395      - `ResponseStatusWaitState` 
396      """ 
397      response_started = False 
398   
403   
 408   
409   
411      """ 
412      Waiting for status line 
413   
414      States to go from here: 
415   
416      - `ResponseHeadersWaitState` 
417      """ 
418      response_started = False 
419   
 425   
426   
428      """ 
429      We're waiting for headers to be set and sent 
430   
431      States to go from here: 
432   
433      - `ResponseBodyWaitState` 
434   
435      :IVariables: 
436       - `_headers`: Ordered list of header names 
437       - `_hdict`: Dict of header names -> values (``{'name': ['value', ...]}``) 
438   
439      :Types: 
440       - `_headers`: ``list`` 
441       - `_hdict`: ``dict`` 
442      """ 
443      response_started = False 
444   
450   
452          """ Send headers """ 
453          for name, value in headers: 
454              name = name.lower() 
455              if name not in self._hdict: 
456                  self._headers.append(name) 
457                  self._hdict[name] = [] 
458              self._hdict[name].append(value) 
 459   
461          """ Finalize headers """ 
462          self._set_state(ResponseBodyWaitState) 
463          request = self._request 
464          if request.protocol >= (1, 0): 
465              out, hdict, writer = [], self._hdict, request.connection.writer 
466              request.response_headers = hdict 
467              request.connection.compute_status() 
468              if request.connection.persist or request.sent_100: 
469                   
470                   
471   
472                  if request.expects_100 and not request.sent_100: 
473                      request.send_continue = self._send_continue 
474                   
475                  stream = request._request_body_stream 
476                  if stream is not None: 
477                      dummy, read = True, stream.read 
478                      while dummy: 
479                          dummy = read(0) 
480                  if request.send_continue == self._send_continue: 
481                      del request.send_continue 
482   
483              hdict.update({ 
484                  'server': ["WTF"], 'date': [_http_util.make_date()] 
485              }) 
486              for key, value in request.connection.headers.iteritems(): 
487                  if key not in hdict: 
488                      self._headers.append(key) 
489                  hdict[key] = [value] 
490   
491              for name in self._headers: 
492                  if name in hdict: 
493                      cname = name.title() 
494                      if name == 'set-cookie': 
495                          out.extend([(cname, val) for val in hdict[name]]) 
496                      else: 
497                          out.append((cname, ", ".join(hdict[name]))) 
498   
499              writer.write( 
500                   
501                  "HTTP/%d.%d " % request.http_version + 
502                      request._response_status_line + CRLF 
503              ) 
504              writer.writelines([ 
505                  "%s: %s%s" % (name, value, CRLF) for name, value in out 
506              ]) 
507              writer.write(CRLF) 
  508   
509   
510 -class ResponseBodyWaitState(BaseState): 
 511      """ 
512      We're waiting for someone to send the response body 
513   
514      States to go from here: 
515   
516      - `ResponseDoneState` 
517      """ 
518      response_started = True 
519   
 535   
536   
540   
541   
543      """ 
544      HTTP Request abstraction 
545   
546      :IVariables: 
547       - `_request_body_stream`: Stream for accessing the request body (or 
548         ``None``). Transfer encodings and the Expect/Continue mechanism 
549         are dealt with transparently. Just read it. 
550       - `_response_body_stream`: Stream for writing the response body (or 
551         ``None``) 
552       - `_server`: HTTP server instance 
553       - `state`: Current state object. Additional methods and properties are 
554         looked up there (see `BaseState` for documentation) 
555       - `headers`: Request header dictionary 
556       - `response_status`: Response status code sent to the client 
557       - `response_headers`: Response headers sent to the client 
558       - `method`: Request method used 
559       - `url`: Request URL 
560       - `protocol`: Request protocol version 
561       - `connection`: HTTP connection abstraction 
562       - `http_version`: Maximum supported HTTP version 
563       - `flags`: Worker flags 
564   
565      :Types: 
566       - `_request_body_stream`: `wtf.stream.GenericStream` 
567       - `_response_body_stream`: `wtf.stream.GenericStream` 
568       - `_server`: `http.HTTPServer` 
569       - `state`: `BaseState` 
570       - `headers`: ``dict`` 
571       - `response_status`: ``int`` 
572       - `response_headers`: ``dict`` 
573       - `method`: ``str`` 
574       - `url`: ``str`` 
575       - `protocol`: ``tuple`` 
576       - `connection`: `HTTPConnection` 
577       - `http_version`: ``tuple`` 
578       - `flags`: `wtf.impl.FlagsInterface` 
579      """ 
580      _request_body_stream, _response_body_stream = None, None 
581      expects_100, sent_100, _response_status_line = False, False, None 
582      headers, response_status, response_headers = None, None, None 
583      method, url, protocol = 'GET', '*', (0, 9) 
584      connection = None 
585   
586 -    def __init__(self, server, connection, flags): 
 587          """ 
588          Initialization 
589   
590          :Parameters: 
591           - `server`: Server instance 
592           - `connection`: Connection, this request is served on 
593           - `flags`: Worker flags 
594   
595          :Types: 
596           - `server`: `HTTPServer` 
597           - `connection`: `Connection` 
598           - `flags`: `FlagsInterface` 
599          """ 
600          self._server = server 
601          self.http_version = server.http_version 
602          self.keep_alive = server.keep_alive 
603          self.flags = flags 
604          self.connection = _http_util.HTTPConnection(self, connection) 
605          self.state = RequestInitialState(self) 
 606   
615   
617          """ 
618          Delegate call to the current state implementation 
619   
620          :Parameters: 
621           - `name`: The symbol to fetch 
622   
623          :Types: 
624           - `name`: ``str`` 
625   
626          :return: The resolved symbol depending on the state (should be a 
627                   callable) 
628          :rtype: any 
629          """ 
630          return getattr(self.state, name) 
 631   
647   
648 -    def error(self, status, message): 
 649          """ 
650          Emit a simple error 
651   
652          :Parameters: 
653           - `status`: The status line to emit, it will be repeated in the body 
654             (which is labeled text/plain for >= HTTP/1.0 or wrapped into HTML 
655             for HTTP/0.9) 
656           - `message`: The message to emit 
657   
658          :Types: 
659           - `status`: ``str`` 
660           - `message`: ``str`` 
661          """ 
662          protocol, write = self.protocol, self.connection.writer.write 
663          if protocol >= (1, 0): 
664              out = status + CRLF + message + CRLF 
665              write("HTTP/%d.%d " % self.http_version + status + CRLF) 
666              write("Date: %s%s" % (_http_util.make_date(), CRLF)) 
667              write("Content-Type: text/plain" + CRLF) 
668              write("Content-Length: %s%s" % (len(out), CRLF)) 
669              if protocol >= (1, 1): 
670                  write("Connection: close" + CRLF) 
671              write(CRLF) 
672              write(out) 
673          else: 
674              out = """ 
675  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> 
676  <html> 
677  <head><title>%(status)s</title></head> 
678  <body><h1>%(status)s</h1><p>%(message)s</p></body> 
679  </html> 
680              """.strip() % { 
681                  'status': status.replace('&', '&').replace('<', '<'), 
682                  'message': message.replace('&', '&').replace('<', '<'), 
683              } 
684              write(out + CRLF) 
  685