testserver.py revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
1#!/usr/bin/env python
2# Copyright 2013 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
7testing Chrome.
8
9It supports several test URLs, as specified by the handlers in TestPageHandler.
10By default, it listens on an ephemeral port and sends the port number back to
11the originating process over a pipe. The originating process can specify an
12explicit port if necessary.
13It can use https if you specify the flag --https=CERT where CERT is the path
14to a pem file containing the certificate and private key that should be used.
15"""
16
17import base64
18import BaseHTTPServer
19import cgi
20import hashlib
21import logging
22import minica
23import os
24import random
25import re
26import select
27import socket
28import SocketServer
29import struct
30import sys
31import threading
32import time
33import urllib
34import urlparse
35import zlib
36
37import echo_message
38import pyftpdlib.ftpserver
39import testserver_base
40import tlslite
41import tlslite.api
42
43BASE_DIR = os.path.dirname(os.path.abspath(__file__))
44sys.path.insert(
45    0, os.path.join(BASE_DIR, '..', '..', '..', 'third_party/pywebsocket/src'))
46from mod_pywebsocket.standalone import WebSocketServer
47
48SERVER_HTTP = 0
49SERVER_FTP = 1
50SERVER_TCP_ECHO = 2
51SERVER_UDP_ECHO = 3
52SERVER_BASIC_AUTH_PROXY = 4
53SERVER_WEBSOCKET = 5
54
55# Default request queue size for WebSocketServer.
56_DEFAULT_REQUEST_QUEUE_SIZE = 128
57
58class WebSocketOptions:
59  """Holds options for WebSocketServer."""
60
61  def __init__(self, host, port, data_dir):
62    self.request_queue_size = _DEFAULT_REQUEST_QUEUE_SIZE
63    self.server_host = host
64    self.port = port
65    self.websock_handlers = data_dir
66    self.scan_dir = None
67    self.allow_handlers_outside_root_dir = False
68    self.websock_handlers_map_file = None
69    self.cgi_directories = []
70    self.is_executable_method = None
71    self.allow_draft75 = False
72    self.strict = True
73
74    self.use_tls = False
75    self.private_key = None
76    self.certificate = None
77    self.tls_client_auth = False
78    self.tls_client_ca = None
79    self.use_basic_auth = False
80
81
82class RecordingSSLSessionCache(object):
83  """RecordingSSLSessionCache acts as a TLS session cache and maintains a log of
84  lookups and inserts in order to test session cache behaviours."""
85
86  def __init__(self):
87    self.log = []
88
89  def __getitem__(self, sessionID):
90    self.log.append(('lookup', sessionID))
91    raise KeyError()
92
93  def __setitem__(self, sessionID, session):
94    self.log.append(('insert', sessionID))
95
96
97class HTTPServer(testserver_base.ClientRestrictingServerMixIn,
98                 testserver_base.BrokenPipeHandlerMixIn,
99                 testserver_base.StoppableHTTPServer):
100  """This is a specialization of StoppableHTTPServer that adds client
101  verification."""
102
103  pass
104
105class OCSPServer(testserver_base.ClientRestrictingServerMixIn,
106                 testserver_base.BrokenPipeHandlerMixIn,
107                 BaseHTTPServer.HTTPServer):
108  """This is a specialization of HTTPServer that serves an
109  OCSP response"""
110
111  def serve_forever_on_thread(self):
112    self.thread = threading.Thread(target = self.serve_forever,
113                                   name = "OCSPServerThread")
114    self.thread.start()
115
116  def stop_serving(self):
117    self.shutdown()
118    self.thread.join()
119
120
121class HTTPSServer(tlslite.api.TLSSocketServerMixIn,
122                  testserver_base.ClientRestrictingServerMixIn,
123                  testserver_base.BrokenPipeHandlerMixIn,
124                  testserver_base.StoppableHTTPServer):
125  """This is a specialization of StoppableHTTPServer that add https support and
126  client verification."""
127
128  def __init__(self, server_address, request_hander_class, pem_cert_and_key,
129               ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers,
130               record_resume_info, tls_intolerant):
131    self.cert_chain = tlslite.api.X509CertChain().parseChain(pem_cert_and_key)
132    self.private_key = tlslite.api.parsePEMKey(pem_cert_and_key, private=True)
133    self.ssl_client_auth = ssl_client_auth
134    self.ssl_client_cas = []
135    self.tls_intolerant = tls_intolerant
136
137    for ca_file in ssl_client_cas:
138      s = open(ca_file).read()
139      x509 = tlslite.api.X509()
140      x509.parse(s)
141      self.ssl_client_cas.append(x509.subject)
142    self.ssl_handshake_settings = tlslite.api.HandshakeSettings()
143    if ssl_bulk_ciphers is not None:
144      self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers
145
146    if record_resume_info:
147      # If record_resume_info is true then we'll replace the session cache with
148      # an object that records the lookups and inserts that it sees.
149      self.session_cache = RecordingSSLSessionCache()
150    else:
151      self.session_cache = tlslite.api.SessionCache()
152    testserver_base.StoppableHTTPServer.__init__(self,
153                                                 server_address,
154                                                 request_hander_class)
155
156  def handshake(self, tlsConnection):
157    """Creates the SSL connection."""
158
159    try:
160      self.tlsConnection = tlsConnection
161      tlsConnection.handshakeServer(certChain=self.cert_chain,
162                                    privateKey=self.private_key,
163                                    sessionCache=self.session_cache,
164                                    reqCert=self.ssl_client_auth,
165                                    settings=self.ssl_handshake_settings,
166                                    reqCAs=self.ssl_client_cas,
167                                    tlsIntolerant=self.tls_intolerant)
168      tlsConnection.ignoreAbruptClose = True
169      return True
170    except tlslite.api.TLSAbruptCloseError:
171      # Ignore abrupt close.
172      return True
173    except tlslite.api.TLSError, error:
174      print "Handshake failure:", str(error)
175      return False
176
177
178class FTPServer(testserver_base.ClientRestrictingServerMixIn,
179                pyftpdlib.ftpserver.FTPServer):
180  """This is a specialization of FTPServer that adds client verification."""
181
182  pass
183
184
185class TCPEchoServer(testserver_base.ClientRestrictingServerMixIn,
186                    SocketServer.TCPServer):
187  """A TCP echo server that echoes back what it has received."""
188
189  def server_bind(self):
190    """Override server_bind to store the server name."""
191
192    SocketServer.TCPServer.server_bind(self)
193    host, port = self.socket.getsockname()[:2]
194    self.server_name = socket.getfqdn(host)
195    self.server_port = port
196
197  def serve_forever(self):
198    self.stop = False
199    self.nonce_time = None
200    while not self.stop:
201      self.handle_request()
202    self.socket.close()
203
204
205class UDPEchoServer(testserver_base.ClientRestrictingServerMixIn,
206                    SocketServer.UDPServer):
207  """A UDP echo server that echoes back what it has received."""
208
209  def server_bind(self):
210    """Override server_bind to store the server name."""
211
212    SocketServer.UDPServer.server_bind(self)
213    host, port = self.socket.getsockname()[:2]
214    self.server_name = socket.getfqdn(host)
215    self.server_port = port
216
217  def serve_forever(self):
218    self.stop = False
219    self.nonce_time = None
220    while not self.stop:
221      self.handle_request()
222    self.socket.close()
223
224
225class TestPageHandler(testserver_base.BasePageHandler):
226  # Class variables to allow for persistence state between page handler
227  # invocations
228  rst_limits = {}
229  fail_precondition = {}
230
231  def __init__(self, request, client_address, socket_server):
232    connect_handlers = [
233      self.RedirectConnectHandler,
234      self.ServerAuthConnectHandler,
235      self.DefaultConnectResponseHandler]
236    get_handlers = [
237      self.NoCacheMaxAgeTimeHandler,
238      self.NoCacheTimeHandler,
239      self.CacheTimeHandler,
240      self.CacheExpiresHandler,
241      self.CacheProxyRevalidateHandler,
242      self.CachePrivateHandler,
243      self.CachePublicHandler,
244      self.CacheSMaxAgeHandler,
245      self.CacheMustRevalidateHandler,
246      self.CacheMustRevalidateMaxAgeHandler,
247      self.CacheNoStoreHandler,
248      self.CacheNoStoreMaxAgeHandler,
249      self.CacheNoTransformHandler,
250      self.DownloadHandler,
251      self.DownloadFinishHandler,
252      self.EchoHeader,
253      self.EchoHeaderCache,
254      self.EchoAllHandler,
255      self.ZipFileHandler,
256      self.FileHandler,
257      self.SetCookieHandler,
258      self.SetManyCookiesHandler,
259      self.ExpectAndSetCookieHandler,
260      self.SetHeaderHandler,
261      self.AuthBasicHandler,
262      self.AuthDigestHandler,
263      self.SlowServerHandler,
264      self.ChunkedServerHandler,
265      self.ContentTypeHandler,
266      self.NoContentHandler,
267      self.ServerRedirectHandler,
268      self.ClientRedirectHandler,
269      self.MultipartHandler,
270      self.MultipartSlowHandler,
271      self.GetSSLSessionCacheHandler,
272      self.SSLManySmallRecords,
273      self.GetChannelID,
274      self.CloseSocketHandler,
275      self.RangeResetHandler,
276      self.DefaultResponseHandler]
277    post_handlers = [
278      self.EchoTitleHandler,
279      self.EchoHandler,
280      self.PostOnlyFileHandler] + get_handlers
281    put_handlers = [
282      self.EchoTitleHandler,
283      self.EchoHandler] + get_handlers
284    head_handlers = [
285      self.FileHandler,
286      self.DefaultResponseHandler]
287
288    self._mime_types = {
289      'crx' : 'application/x-chrome-extension',
290      'exe' : 'application/octet-stream',
291      'gif': 'image/gif',
292      'jpeg' : 'image/jpeg',
293      'jpg' : 'image/jpeg',
294      'json': 'application/json',
295      'pdf' : 'application/pdf',
296      'wav' : 'audio/wav',
297      'xml' : 'text/xml'
298    }
299    self._default_mime_type = 'text/html'
300
301    testserver_base.BasePageHandler.__init__(self, request, client_address,
302                                             socket_server, connect_handlers,
303                                             get_handlers, head_handlers,
304                                             post_handlers, put_handlers)
305
306  def GetMIMETypeFromName(self, file_name):
307    """Returns the mime type for the specified file_name. So far it only looks
308    at the file extension."""
309
310    (_shortname, extension) = os.path.splitext(file_name.split("?")[0])
311    if len(extension) == 0:
312      # no extension.
313      return self._default_mime_type
314
315    # extension starts with a dot, so we need to remove it
316    return self._mime_types.get(extension[1:], self._default_mime_type)
317
318  def NoCacheMaxAgeTimeHandler(self):
319    """This request handler yields a page with the title set to the current
320    system time, and no caching requested."""
321
322    if not self._ShouldHandleRequest("/nocachetime/maxage"):
323      return False
324
325    self.send_response(200)
326    self.send_header('Cache-Control', 'max-age=0')
327    self.send_header('Content-Type', 'text/html')
328    self.end_headers()
329
330    self.wfile.write('<html><head><title>%s</title></head></html>' %
331                     time.time())
332
333    return True
334
335  def NoCacheTimeHandler(self):
336    """This request handler yields a page with the title set to the current
337    system time, and no caching requested."""
338
339    if not self._ShouldHandleRequest("/nocachetime"):
340      return False
341
342    self.send_response(200)
343    self.send_header('Cache-Control', 'no-cache')
344    self.send_header('Content-Type', 'text/html')
345    self.end_headers()
346
347    self.wfile.write('<html><head><title>%s</title></head></html>' %
348                     time.time())
349
350    return True
351
352  def CacheTimeHandler(self):
353    """This request handler yields a page with the title set to the current
354    system time, and allows caching for one minute."""
355
356    if not self._ShouldHandleRequest("/cachetime"):
357      return False
358
359    self.send_response(200)
360    self.send_header('Cache-Control', 'max-age=60')
361    self.send_header('Content-Type', 'text/html')
362    self.end_headers()
363
364    self.wfile.write('<html><head><title>%s</title></head></html>' %
365                     time.time())
366
367    return True
368
369  def CacheExpiresHandler(self):
370    """This request handler yields a page with the title set to the current
371    system time, and set the page to expire on 1 Jan 2099."""
372
373    if not self._ShouldHandleRequest("/cache/expires"):
374      return False
375
376    self.send_response(200)
377    self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
378    self.send_header('Content-Type', 'text/html')
379    self.end_headers()
380
381    self.wfile.write('<html><head><title>%s</title></head></html>' %
382                     time.time())
383
384    return True
385
386  def CacheProxyRevalidateHandler(self):
387    """This request handler yields a page with the title set to the current
388    system time, and allows caching for 60 seconds"""
389
390    if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
391      return False
392
393    self.send_response(200)
394    self.send_header('Content-Type', 'text/html')
395    self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
396    self.end_headers()
397
398    self.wfile.write('<html><head><title>%s</title></head></html>' %
399                     time.time())
400
401    return True
402
403  def CachePrivateHandler(self):
404    """This request handler yields a page with the title set to the current
405    system time, and allows caching for 5 seconds."""
406
407    if not self._ShouldHandleRequest("/cache/private"):
408      return False
409
410    self.send_response(200)
411    self.send_header('Content-Type', 'text/html')
412    self.send_header('Cache-Control', 'max-age=3, private')
413    self.end_headers()
414
415    self.wfile.write('<html><head><title>%s</title></head></html>' %
416                     time.time())
417
418    return True
419
420  def CachePublicHandler(self):
421    """This request handler yields a page with the title set to the current
422    system time, and allows caching for 5 seconds."""
423
424    if not self._ShouldHandleRequest("/cache/public"):
425      return False
426
427    self.send_response(200)
428    self.send_header('Content-Type', 'text/html')
429    self.send_header('Cache-Control', 'max-age=3, public')
430    self.end_headers()
431
432    self.wfile.write('<html><head><title>%s</title></head></html>' %
433                     time.time())
434
435    return True
436
437  def CacheSMaxAgeHandler(self):
438    """This request handler yields a page with the title set to the current
439    system time, and does not allow for caching."""
440
441    if not self._ShouldHandleRequest("/cache/s-maxage"):
442      return False
443
444    self.send_response(200)
445    self.send_header('Content-Type', 'text/html')
446    self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
447    self.end_headers()
448
449    self.wfile.write('<html><head><title>%s</title></head></html>' %
450                     time.time())
451
452    return True
453
454  def CacheMustRevalidateHandler(self):
455    """This request handler yields a page with the title set to the current
456    system time, and does not allow caching."""
457
458    if not self._ShouldHandleRequest("/cache/must-revalidate"):
459      return False
460
461    self.send_response(200)
462    self.send_header('Content-Type', 'text/html')
463    self.send_header('Cache-Control', 'must-revalidate')
464    self.end_headers()
465
466    self.wfile.write('<html><head><title>%s</title></head></html>' %
467                     time.time())
468
469    return True
470
471  def CacheMustRevalidateMaxAgeHandler(self):
472    """This request handler yields a page with the title set to the current
473    system time, and does not allow caching event though max-age of 60
474    seconds is specified."""
475
476    if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
477      return False
478
479    self.send_response(200)
480    self.send_header('Content-Type', 'text/html')
481    self.send_header('Cache-Control', 'max-age=60, must-revalidate')
482    self.end_headers()
483
484    self.wfile.write('<html><head><title>%s</title></head></html>' %
485                     time.time())
486
487    return True
488
489  def CacheNoStoreHandler(self):
490    """This request handler yields a page with the title set to the current
491    system time, and does not allow the page to be stored."""
492
493    if not self._ShouldHandleRequest("/cache/no-store"):
494      return False
495
496    self.send_response(200)
497    self.send_header('Content-Type', 'text/html')
498    self.send_header('Cache-Control', 'no-store')
499    self.end_headers()
500
501    self.wfile.write('<html><head><title>%s</title></head></html>' %
502                     time.time())
503
504    return True
505
506  def CacheNoStoreMaxAgeHandler(self):
507    """This request handler yields a page with the title set to the current
508    system time, and does not allow the page to be stored even though max-age
509    of 60 seconds is specified."""
510
511    if not self._ShouldHandleRequest("/cache/no-store/max-age"):
512      return False
513
514    self.send_response(200)
515    self.send_header('Content-Type', 'text/html')
516    self.send_header('Cache-Control', 'max-age=60, no-store')
517    self.end_headers()
518
519    self.wfile.write('<html><head><title>%s</title></head></html>' %
520                     time.time())
521
522    return True
523
524
525  def CacheNoTransformHandler(self):
526    """This request handler yields a page with the title set to the current
527    system time, and does not allow the content to transformed during
528    user-agent caching"""
529
530    if not self._ShouldHandleRequest("/cache/no-transform"):
531      return False
532
533    self.send_response(200)
534    self.send_header('Content-Type', 'text/html')
535    self.send_header('Cache-Control', 'no-transform')
536    self.end_headers()
537
538    self.wfile.write('<html><head><title>%s</title></head></html>' %
539                     time.time())
540
541    return True
542
543  def EchoHeader(self):
544    """This handler echoes back the value of a specific request header."""
545
546    return self.EchoHeaderHelper("/echoheader")
547
548  def EchoHeaderCache(self):
549    """This function echoes back the value of a specific request header while
550    allowing caching for 16 hours."""
551
552    return self.EchoHeaderHelper("/echoheadercache")
553
554  def EchoHeaderHelper(self, echo_header):
555    """This function echoes back the value of the request header passed in."""
556
557    if not self._ShouldHandleRequest(echo_header):
558      return False
559
560    query_char = self.path.find('?')
561    if query_char != -1:
562      header_name = self.path[query_char+1:]
563
564    self.send_response(200)
565    self.send_header('Content-Type', 'text/plain')
566    if echo_header == '/echoheadercache':
567      self.send_header('Cache-control', 'max-age=60000')
568    else:
569      self.send_header('Cache-control', 'no-cache')
570    # insert a vary header to properly indicate that the cachability of this
571    # request is subject to value of the request header being echoed.
572    if len(header_name) > 0:
573      self.send_header('Vary', header_name)
574    self.end_headers()
575
576    if len(header_name) > 0:
577      self.wfile.write(self.headers.getheader(header_name))
578
579    return True
580
581  def ReadRequestBody(self):
582    """This function reads the body of the current HTTP request, handling
583    both plain and chunked transfer encoded requests."""
584
585    if self.headers.getheader('transfer-encoding') != 'chunked':
586      length = int(self.headers.getheader('content-length'))
587      return self.rfile.read(length)
588
589    # Read the request body as chunks.
590    body = ""
591    while True:
592      line = self.rfile.readline()
593      length = int(line, 16)
594      if length == 0:
595        self.rfile.readline()
596        break
597      body += self.rfile.read(length)
598      self.rfile.read(2)
599    return body
600
601  def EchoHandler(self):
602    """This handler just echoes back the payload of the request, for testing
603    form submission."""
604
605    if not self._ShouldHandleRequest("/echo"):
606      return False
607
608    self.send_response(200)
609    self.send_header('Content-Type', 'text/html')
610    self.end_headers()
611    self.wfile.write(self.ReadRequestBody())
612    return True
613
614  def EchoTitleHandler(self):
615    """This handler is like Echo, but sets the page title to the request."""
616
617    if not self._ShouldHandleRequest("/echotitle"):
618      return False
619
620    self.send_response(200)
621    self.send_header('Content-Type', 'text/html')
622    self.end_headers()
623    request = self.ReadRequestBody()
624    self.wfile.write('<html><head><title>')
625    self.wfile.write(request)
626    self.wfile.write('</title></head></html>')
627    return True
628
629  def EchoAllHandler(self):
630    """This handler yields a (more) human-readable page listing information
631    about the request header & contents."""
632
633    if not self._ShouldHandleRequest("/echoall"):
634      return False
635
636    self.send_response(200)
637    self.send_header('Content-Type', 'text/html')
638    self.end_headers()
639    self.wfile.write('<html><head><style>'
640      'pre { border: 1px solid black; margin: 5px; padding: 5px }'
641      '</style></head><body>'
642      '<div style="float: right">'
643      '<a href="/echo">back to referring page</a></div>'
644      '<h1>Request Body:</h1><pre>')
645
646    if self.command == 'POST' or self.command == 'PUT':
647      qs = self.ReadRequestBody()
648      params = cgi.parse_qs(qs, keep_blank_values=1)
649
650      for param in params:
651        self.wfile.write('%s=%s\n' % (param, params[param][0]))
652
653    self.wfile.write('</pre>')
654
655    self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
656
657    self.wfile.write('</body></html>')
658    return True
659
660  def DownloadHandler(self):
661    """This handler sends a downloadable file with or without reporting
662    the size (6K)."""
663
664    if self.path.startswith("/download-unknown-size"):
665      send_length = False
666    elif self.path.startswith("/download-known-size"):
667      send_length = True
668    else:
669      return False
670
671    #
672    # The test which uses this functionality is attempting to send
673    # small chunks of data to the client.  Use a fairly large buffer
674    # so that we'll fill chrome's IO buffer enough to force it to
675    # actually write the data.
676    # See also the comments in the client-side of this test in
677    # download_uitest.cc
678    #
679    size_chunk1 = 35*1024
680    size_chunk2 = 10*1024
681
682    self.send_response(200)
683    self.send_header('Content-Type', 'application/octet-stream')
684    self.send_header('Cache-Control', 'max-age=0')
685    if send_length:
686      self.send_header('Content-Length', size_chunk1 + size_chunk2)
687    self.end_headers()
688
689    # First chunk of data:
690    self.wfile.write("*" * size_chunk1)
691    self.wfile.flush()
692
693    # handle requests until one of them clears this flag.
694    self.server.wait_for_download = True
695    while self.server.wait_for_download:
696      self.server.handle_request()
697
698    # Second chunk of data:
699    self.wfile.write("*" * size_chunk2)
700    return True
701
702  def DownloadFinishHandler(self):
703    """This handler just tells the server to finish the current download."""
704
705    if not self._ShouldHandleRequest("/download-finish"):
706      return False
707
708    self.server.wait_for_download = False
709    self.send_response(200)
710    self.send_header('Content-Type', 'text/html')
711    self.send_header('Cache-Control', 'max-age=0')
712    self.end_headers()
713    return True
714
715  def _ReplaceFileData(self, data, query_parameters):
716    """Replaces matching substrings in a file.
717
718    If the 'replace_text' URL query parameter is present, it is expected to be
719    of the form old_text:new_text, which indicates that any old_text strings in
720    the file are replaced with new_text. Multiple 'replace_text' parameters may
721    be specified.
722
723    If the parameters are not present, |data| is returned.
724    """
725
726    query_dict = cgi.parse_qs(query_parameters)
727    replace_text_values = query_dict.get('replace_text', [])
728    for replace_text_value in replace_text_values:
729      replace_text_args = replace_text_value.split(':')
730      if len(replace_text_args) != 2:
731        raise ValueError(
732          'replace_text must be of form old_text:new_text. Actual value: %s' %
733          replace_text_value)
734      old_text_b64, new_text_b64 = replace_text_args
735      old_text = base64.urlsafe_b64decode(old_text_b64)
736      new_text = base64.urlsafe_b64decode(new_text_b64)
737      data = data.replace(old_text, new_text)
738    return data
739
740  def ZipFileHandler(self):
741    """This handler sends the contents of the requested file in compressed form.
742    Can pass in a parameter that specifies that the content length be
743    C - the compressed size (OK),
744    U - the uncompressed size (Non-standard, but handled),
745    S - less than compressed (OK because we keep going),
746    M - larger than compressed but less than uncompressed (an error),
747    L - larger than uncompressed (an error)
748    Example: compressedfiles/Picture_1.doc?C
749    """
750
751    prefix = "/compressedfiles/"
752    if not self.path.startswith(prefix):
753      return False
754
755    # Consume a request body if present.
756    if self.command == 'POST' or self.command == 'PUT' :
757      self.ReadRequestBody()
758
759    _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
760
761    if not query in ('C', 'U', 'S', 'M', 'L'):
762      return False
763
764    sub_path = url_path[len(prefix):]
765    entries = sub_path.split('/')
766    file_path = os.path.join(self.server.data_dir, *entries)
767    if os.path.isdir(file_path):
768      file_path = os.path.join(file_path, 'index.html')
769
770    if not os.path.isfile(file_path):
771      print "File not found " + sub_path + " full path:" + file_path
772      self.send_error(404)
773      return True
774
775    f = open(file_path, "rb")
776    data = f.read()
777    uncompressed_len = len(data)
778    f.close()
779
780    # Compress the data.
781    data = zlib.compress(data)
782    compressed_len = len(data)
783
784    content_length = compressed_len
785    if query == 'U':
786      content_length = uncompressed_len
787    elif query == 'S':
788      content_length = compressed_len / 2
789    elif query == 'M':
790      content_length = (compressed_len + uncompressed_len) / 2
791    elif query == 'L':
792      content_length = compressed_len + uncompressed_len
793
794    self.send_response(200)
795    self.send_header('Content-Type', 'application/msword')
796    self.send_header('Content-encoding', 'deflate')
797    self.send_header('Connection', 'close')
798    self.send_header('Content-Length', content_length)
799    self.send_header('ETag', '\'' + file_path + '\'')
800    self.end_headers()
801
802    self.wfile.write(data)
803
804    return True
805
806  def FileHandler(self):
807    """This handler sends the contents of the requested file.  Wow, it's like
808    a real webserver!"""
809
810    prefix = self.server.file_root_url
811    if not self.path.startswith(prefix):
812      return False
813    return self._FileHandlerHelper(prefix)
814
815  def PostOnlyFileHandler(self):
816    """This handler sends the contents of the requested file on a POST."""
817
818    prefix = urlparse.urljoin(self.server.file_root_url, 'post/')
819    if not self.path.startswith(prefix):
820      return False
821    return self._FileHandlerHelper(prefix)
822
823  def _FileHandlerHelper(self, prefix):
824    request_body = ''
825    if self.command == 'POST' or self.command == 'PUT':
826      # Consume a request body if present.
827      request_body = self.ReadRequestBody()
828
829    _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
830    query_dict = cgi.parse_qs(query)
831
832    expected_body = query_dict.get('expected_body', [])
833    if expected_body and request_body not in expected_body:
834      self.send_response(404)
835      self.end_headers()
836      self.wfile.write('')
837      return True
838
839    expected_headers = query_dict.get('expected_headers', [])
840    for expected_header in expected_headers:
841      header_name, expected_value = expected_header.split(':')
842      if self.headers.getheader(header_name) != expected_value:
843        self.send_response(404)
844        self.end_headers()
845        self.wfile.write('')
846        return True
847
848    sub_path = url_path[len(prefix):]
849    entries = sub_path.split('/')
850    file_path = os.path.join(self.server.data_dir, *entries)
851    if os.path.isdir(file_path):
852      file_path = os.path.join(file_path, 'index.html')
853
854    if not os.path.isfile(file_path):
855      print "File not found " + sub_path + " full path:" + file_path
856      self.send_error(404)
857      return True
858
859    f = open(file_path, "rb")
860    data = f.read()
861    f.close()
862
863    data = self._ReplaceFileData(data, query)
864
865    old_protocol_version = self.protocol_version
866
867    # If file.mock-http-headers exists, it contains the headers we
868    # should send.  Read them in and parse them.
869    headers_path = file_path + '.mock-http-headers'
870    if os.path.isfile(headers_path):
871      f = open(headers_path, "r")
872
873      # "HTTP/1.1 200 OK"
874      response = f.readline()
875      http_major, http_minor, status_code = re.findall(
876          'HTTP/(\d+).(\d+) (\d+)', response)[0]
877      self.protocol_version = "HTTP/%s.%s" % (http_major, http_minor)
878      self.send_response(int(status_code))
879
880      for line in f:
881        header_values = re.findall('(\S+):\s*(.*)', line)
882        if len(header_values) > 0:
883          # "name: value"
884          name, value = header_values[0]
885          self.send_header(name, value)
886      f.close()
887    else:
888      # Could be more generic once we support mime-type sniffing, but for
889      # now we need to set it explicitly.
890
891      range_header = self.headers.get('Range')
892      if range_header and range_header.startswith('bytes='):
893        # Note this doesn't handle all valid byte range_header values (i.e.
894        # left open ended ones), just enough for what we needed so far.
895        range_header = range_header[6:].split('-')
896        start = int(range_header[0])
897        if range_header[1]:
898          end = int(range_header[1])
899        else:
900          end = len(data) - 1
901
902        self.send_response(206)
903        content_range = ('bytes ' + str(start) + '-' + str(end) + '/' +
904                         str(len(data)))
905        self.send_header('Content-Range', content_range)
906        data = data[start: end + 1]
907      else:
908        self.send_response(200)
909
910      self.send_header('Content-Type', self.GetMIMETypeFromName(file_path))
911      self.send_header('Accept-Ranges', 'bytes')
912      self.send_header('Content-Length', len(data))
913      self.send_header('ETag', '\'' + file_path + '\'')
914    self.end_headers()
915
916    if (self.command != 'HEAD'):
917      self.wfile.write(data)
918
919    self.protocol_version = old_protocol_version
920    return True
921
922  def SetCookieHandler(self):
923    """This handler just sets a cookie, for testing cookie handling."""
924
925    if not self._ShouldHandleRequest("/set-cookie"):
926      return False
927
928    query_char = self.path.find('?')
929    if query_char != -1:
930      cookie_values = self.path[query_char + 1:].split('&')
931    else:
932      cookie_values = ("",)
933    self.send_response(200)
934    self.send_header('Content-Type', 'text/html')
935    for cookie_value in cookie_values:
936      self.send_header('Set-Cookie', '%s' % cookie_value)
937    self.end_headers()
938    for cookie_value in cookie_values:
939      self.wfile.write('%s' % cookie_value)
940    return True
941
942  def SetManyCookiesHandler(self):
943    """This handler just sets a given number of cookies, for testing handling
944       of large numbers of cookies."""
945
946    if not self._ShouldHandleRequest("/set-many-cookies"):
947      return False
948
949    query_char = self.path.find('?')
950    if query_char != -1:
951      num_cookies = int(self.path[query_char + 1:])
952    else:
953      num_cookies = 0
954    self.send_response(200)
955    self.send_header('', 'text/html')
956    for _i in range(0, num_cookies):
957      self.send_header('Set-Cookie', 'a=')
958    self.end_headers()
959    self.wfile.write('%d cookies were sent' % num_cookies)
960    return True
961
962  def ExpectAndSetCookieHandler(self):
963    """Expects some cookies to be sent, and if they are, sets more cookies.
964
965    The expect parameter specifies a required cookie.  May be specified multiple
966    times.
967    The set parameter specifies a cookie to set if all required cookies are
968    preset.  May be specified multiple times.
969    The data parameter specifies the response body data to be returned."""
970
971    if not self._ShouldHandleRequest("/expect-and-set-cookie"):
972      return False
973
974    _, _, _, _, query, _ = urlparse.urlparse(self.path)
975    query_dict = cgi.parse_qs(query)
976    cookies = set()
977    if 'Cookie' in self.headers:
978      cookie_header = self.headers.getheader('Cookie')
979      cookies.update([s.strip() for s in cookie_header.split(';')])
980    got_all_expected_cookies = True
981    for expected_cookie in query_dict.get('expect', []):
982      if expected_cookie not in cookies:
983        got_all_expected_cookies = False
984    self.send_response(200)
985    self.send_header('Content-Type', 'text/html')
986    if got_all_expected_cookies:
987      for cookie_value in query_dict.get('set', []):
988        self.send_header('Set-Cookie', '%s' % cookie_value)
989    self.end_headers()
990    for data_value in query_dict.get('data', []):
991      self.wfile.write(data_value)
992    return True
993
994  def SetHeaderHandler(self):
995    """This handler sets a response header. Parameters are in the
996    key%3A%20value&key2%3A%20value2 format."""
997
998    if not self._ShouldHandleRequest("/set-header"):
999      return False
1000
1001    query_char = self.path.find('?')
1002    if query_char != -1:
1003      headers_values = self.path[query_char + 1:].split('&')
1004    else:
1005      headers_values = ("",)
1006    self.send_response(200)
1007    self.send_header('Content-Type', 'text/html')
1008    for header_value in headers_values:
1009      header_value = urllib.unquote(header_value)
1010      (key, value) = header_value.split(': ', 1)
1011      self.send_header(key, value)
1012    self.end_headers()
1013    for header_value in headers_values:
1014      self.wfile.write('%s' % header_value)
1015    return True
1016
1017  def AuthBasicHandler(self):
1018    """This handler tests 'Basic' authentication.  It just sends a page with
1019    title 'user/pass' if you succeed."""
1020
1021    if not self._ShouldHandleRequest("/auth-basic"):
1022      return False
1023
1024    username = userpass = password = b64str = ""
1025    expected_password = 'secret'
1026    realm = 'testrealm'
1027    set_cookie_if_challenged = False
1028
1029    _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1030    query_params = cgi.parse_qs(query, True)
1031    if 'set-cookie-if-challenged' in query_params:
1032      set_cookie_if_challenged = True
1033    if 'password' in query_params:
1034      expected_password = query_params['password'][0]
1035    if 'realm' in query_params:
1036      realm = query_params['realm'][0]
1037
1038    auth = self.headers.getheader('authorization')
1039    try:
1040      if not auth:
1041        raise Exception('no auth')
1042      b64str = re.findall(r'Basic (\S+)', auth)[0]
1043      userpass = base64.b64decode(b64str)
1044      username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
1045      if password != expected_password:
1046        raise Exception('wrong password')
1047    except Exception, e:
1048      # Authentication failed.
1049      self.send_response(401)
1050      self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm)
1051      self.send_header('Content-Type', 'text/html')
1052      if set_cookie_if_challenged:
1053        self.send_header('Set-Cookie', 'got_challenged=true')
1054      self.end_headers()
1055      self.wfile.write('<html><head>')
1056      self.wfile.write('<title>Denied: %s</title>' % e)
1057      self.wfile.write('</head><body>')
1058      self.wfile.write('auth=%s<p>' % auth)
1059      self.wfile.write('b64str=%s<p>' % b64str)
1060      self.wfile.write('username: %s<p>' % username)
1061      self.wfile.write('userpass: %s<p>' % userpass)
1062      self.wfile.write('password: %s<p>' % password)
1063      self.wfile.write('You sent:<br>%s<p>' % self.headers)
1064      self.wfile.write('</body></html>')
1065      return True
1066
1067    # Authentication successful.  (Return a cachable response to allow for
1068    # testing cached pages that require authentication.)
1069    old_protocol_version = self.protocol_version
1070    self.protocol_version = "HTTP/1.1"
1071
1072    if_none_match = self.headers.getheader('if-none-match')
1073    if if_none_match == "abc":
1074      self.send_response(304)
1075      self.end_headers()
1076    elif url_path.endswith(".gif"):
1077      # Using chrome/test/data/google/logo.gif as the test image
1078      test_image_path = ['google', 'logo.gif']
1079      gif_path = os.path.join(self.server.data_dir, *test_image_path)
1080      if not os.path.isfile(gif_path):
1081        self.send_error(404)
1082        self.protocol_version = old_protocol_version
1083        return True
1084
1085      f = open(gif_path, "rb")
1086      data = f.read()
1087      f.close()
1088
1089      self.send_response(200)
1090      self.send_header('Content-Type', 'image/gif')
1091      self.send_header('Cache-control', 'max-age=60000')
1092      self.send_header('Etag', 'abc')
1093      self.end_headers()
1094      self.wfile.write(data)
1095    else:
1096      self.send_response(200)
1097      self.send_header('Content-Type', 'text/html')
1098      self.send_header('Cache-control', 'max-age=60000')
1099      self.send_header('Etag', 'abc')
1100      self.end_headers()
1101      self.wfile.write('<html><head>')
1102      self.wfile.write('<title>%s/%s</title>' % (username, password))
1103      self.wfile.write('</head><body>')
1104      self.wfile.write('auth=%s<p>' % auth)
1105      self.wfile.write('You sent:<br>%s<p>' % self.headers)
1106      self.wfile.write('</body></html>')
1107
1108    self.protocol_version = old_protocol_version
1109    return True
1110
1111  def GetNonce(self, force_reset=False):
1112    """Returns a nonce that's stable per request path for the server's lifetime.
1113    This is a fake implementation. A real implementation would only use a given
1114    nonce a single time (hence the name n-once). However, for the purposes of
1115    unittesting, we don't care about the security of the nonce.
1116
1117    Args:
1118      force_reset: Iff set, the nonce will be changed. Useful for testing the
1119          "stale" response.
1120    """
1121
1122    if force_reset or not self.server.nonce_time:
1123      self.server.nonce_time = time.time()
1124    return hashlib.md5('privatekey%s%d' %
1125                       (self.path, self.server.nonce_time)).hexdigest()
1126
1127  def AuthDigestHandler(self):
1128    """This handler tests 'Digest' authentication.
1129
1130    It just sends a page with title 'user/pass' if you succeed.
1131
1132    A stale response is sent iff "stale" is present in the request path.
1133    """
1134
1135    if not self._ShouldHandleRequest("/auth-digest"):
1136      return False
1137
1138    stale = 'stale' in self.path
1139    nonce = self.GetNonce(force_reset=stale)
1140    opaque = hashlib.md5('opaque').hexdigest()
1141    password = 'secret'
1142    realm = 'testrealm'
1143
1144    auth = self.headers.getheader('authorization')
1145    pairs = {}
1146    try:
1147      if not auth:
1148        raise Exception('no auth')
1149      if not auth.startswith('Digest'):
1150        raise Exception('not digest')
1151      # Pull out all the name="value" pairs as a dictionary.
1152      pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
1153
1154      # Make sure it's all valid.
1155      if pairs['nonce'] != nonce:
1156        raise Exception('wrong nonce')
1157      if pairs['opaque'] != opaque:
1158        raise Exception('wrong opaque')
1159
1160      # Check the 'response' value and make sure it matches our magic hash.
1161      # See http://www.ietf.org/rfc/rfc2617.txt
1162      hash_a1 = hashlib.md5(
1163          ':'.join([pairs['username'], realm, password])).hexdigest()
1164      hash_a2 = hashlib.md5(':'.join([self.command, pairs['uri']])).hexdigest()
1165      if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
1166        response = hashlib.md5(':'.join([hash_a1, nonce, pairs['nc'],
1167            pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
1168      else:
1169        response = hashlib.md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
1170
1171      if pairs['response'] != response:
1172        raise Exception('wrong password')
1173    except Exception, e:
1174      # Authentication failed.
1175      self.send_response(401)
1176      hdr = ('Digest '
1177             'realm="%s", '
1178             'domain="/", '
1179             'qop="auth", '
1180             'algorithm=MD5, '
1181             'nonce="%s", '
1182             'opaque="%s"') % (realm, nonce, opaque)
1183      if stale:
1184        hdr += ', stale="TRUE"'
1185      self.send_header('WWW-Authenticate', hdr)
1186      self.send_header('Content-Type', 'text/html')
1187      self.end_headers()
1188      self.wfile.write('<html><head>')
1189      self.wfile.write('<title>Denied: %s</title>' % e)
1190      self.wfile.write('</head><body>')
1191      self.wfile.write('auth=%s<p>' % auth)
1192      self.wfile.write('pairs=%s<p>' % pairs)
1193      self.wfile.write('You sent:<br>%s<p>' % self.headers)
1194      self.wfile.write('We are replying:<br>%s<p>' % hdr)
1195      self.wfile.write('</body></html>')
1196      return True
1197
1198    # Authentication successful.
1199    self.send_response(200)
1200    self.send_header('Content-Type', 'text/html')
1201    self.end_headers()
1202    self.wfile.write('<html><head>')
1203    self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
1204    self.wfile.write('</head><body>')
1205    self.wfile.write('auth=%s<p>' % auth)
1206    self.wfile.write('pairs=%s<p>' % pairs)
1207    self.wfile.write('</body></html>')
1208
1209    return True
1210
1211  def SlowServerHandler(self):
1212    """Wait for the user suggested time before responding. The syntax is
1213    /slow?0.5 to wait for half a second."""
1214
1215    if not self._ShouldHandleRequest("/slow"):
1216      return False
1217    query_char = self.path.find('?')
1218    wait_sec = 1.0
1219    if query_char >= 0:
1220      try:
1221        wait_sec = int(self.path[query_char + 1:])
1222      except ValueError:
1223        pass
1224    time.sleep(wait_sec)
1225    self.send_response(200)
1226    self.send_header('Content-Type', 'text/plain')
1227    self.end_headers()
1228    self.wfile.write("waited %d seconds" % wait_sec)
1229    return True
1230
1231  def ChunkedServerHandler(self):
1232    """Send chunked response. Allows to specify chunks parameters:
1233     - waitBeforeHeaders - ms to wait before sending headers
1234     - waitBetweenChunks - ms to wait between chunks
1235     - chunkSize - size of each chunk in bytes
1236     - chunksNumber - number of chunks
1237    Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1238    waits one second, then sends headers and five chunks five bytes each."""
1239
1240    if not self._ShouldHandleRequest("/chunked"):
1241      return False
1242    query_char = self.path.find('?')
1243    chunkedSettings = {'waitBeforeHeaders' : 0,
1244                       'waitBetweenChunks' : 0,
1245                       'chunkSize' : 5,
1246                       'chunksNumber' : 5}
1247    if query_char >= 0:
1248      params = self.path[query_char + 1:].split('&')
1249      for param in params:
1250        keyValue = param.split('=')
1251        if len(keyValue) == 2:
1252          try:
1253            chunkedSettings[keyValue[0]] = int(keyValue[1])
1254          except ValueError:
1255            pass
1256    time.sleep(0.001 * chunkedSettings['waitBeforeHeaders'])
1257    self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding
1258    self.send_response(200)
1259    self.send_header('Content-Type', 'text/plain')
1260    self.send_header('Connection', 'close')
1261    self.send_header('Transfer-Encoding', 'chunked')
1262    self.end_headers()
1263    # Chunked encoding: sending all chunks, then final zero-length chunk and
1264    # then final CRLF.
1265    for i in range(0, chunkedSettings['chunksNumber']):
1266      if i > 0:
1267        time.sleep(0.001 * chunkedSettings['waitBetweenChunks'])
1268      self.sendChunkHelp('*' * chunkedSettings['chunkSize'])
1269      self.wfile.flush() # Keep in mind that we start flushing only after 1kb.
1270    self.sendChunkHelp('')
1271    return True
1272
1273  def ContentTypeHandler(self):
1274    """Returns a string of html with the given content type.  E.g.,
1275    /contenttype?text/css returns an html file with the Content-Type
1276    header set to text/css."""
1277
1278    if not self._ShouldHandleRequest("/contenttype"):
1279      return False
1280    query_char = self.path.find('?')
1281    content_type = self.path[query_char + 1:].strip()
1282    if not content_type:
1283      content_type = 'text/html'
1284    self.send_response(200)
1285    self.send_header('Content-Type', content_type)
1286    self.end_headers()
1287    self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
1288    return True
1289
1290  def NoContentHandler(self):
1291    """Returns a 204 No Content response."""
1292
1293    if not self._ShouldHandleRequest("/nocontent"):
1294      return False
1295    self.send_response(204)
1296    self.end_headers()
1297    return True
1298
1299  def ServerRedirectHandler(self):
1300    """Sends a server redirect to the given URL. The syntax is
1301    '/server-redirect?http://foo.bar/asdf' to redirect to
1302    'http://foo.bar/asdf'"""
1303
1304    test_name = "/server-redirect"
1305    if not self._ShouldHandleRequest(test_name):
1306      return False
1307
1308    query_char = self.path.find('?')
1309    if query_char < 0 or len(self.path) <= query_char + 1:
1310      self.sendRedirectHelp(test_name)
1311      return True
1312    dest = self.path[query_char + 1:]
1313
1314    self.send_response(301)  # moved permanently
1315    self.send_header('Location', dest)
1316    self.send_header('Content-Type', 'text/html')
1317    self.end_headers()
1318    self.wfile.write('<html><head>')
1319    self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1320
1321    return True
1322
1323  def ClientRedirectHandler(self):
1324    """Sends a client redirect to the given URL. The syntax is
1325    '/client-redirect?http://foo.bar/asdf' to redirect to
1326    'http://foo.bar/asdf'"""
1327
1328    test_name = "/client-redirect"
1329    if not self._ShouldHandleRequest(test_name):
1330      return False
1331
1332    query_char = self.path.find('?')
1333    if query_char < 0 or len(self.path) <= query_char + 1:
1334      self.sendRedirectHelp(test_name)
1335      return True
1336    dest = self.path[query_char + 1:]
1337
1338    self.send_response(200)
1339    self.send_header('Content-Type', 'text/html')
1340    self.end_headers()
1341    self.wfile.write('<html><head>')
1342    self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
1343    self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
1344
1345    return True
1346
1347  def MultipartHandler(self):
1348    """Send a multipart response (10 text/html pages)."""
1349
1350    test_name = '/multipart'
1351    if not self._ShouldHandleRequest(test_name):
1352      return False
1353
1354    num_frames = 10
1355    bound = '12345'
1356    self.send_response(200)
1357    self.send_header('Content-Type',
1358                     'multipart/x-mixed-replace;boundary=' + bound)
1359    self.end_headers()
1360
1361    for i in xrange(num_frames):
1362      self.wfile.write('--' + bound + '\r\n')
1363      self.wfile.write('Content-Type: text/html\r\n\r\n')
1364      self.wfile.write('<title>page ' + str(i) + '</title>')
1365      self.wfile.write('page ' + str(i))
1366
1367    self.wfile.write('--' + bound + '--')
1368    return True
1369
1370  def MultipartSlowHandler(self):
1371    """Send a multipart response (3 text/html pages) with a slight delay
1372    between each page.  This is similar to how some pages show status using
1373    multipart."""
1374
1375    test_name = '/multipart-slow'
1376    if not self._ShouldHandleRequest(test_name):
1377      return False
1378
1379    num_frames = 3
1380    bound = '12345'
1381    self.send_response(200)
1382    self.send_header('Content-Type',
1383                     'multipart/x-mixed-replace;boundary=' + bound)
1384    self.end_headers()
1385
1386    for i in xrange(num_frames):
1387      self.wfile.write('--' + bound + '\r\n')
1388      self.wfile.write('Content-Type: text/html\r\n\r\n')
1389      time.sleep(0.25)
1390      if i == 2:
1391        self.wfile.write('<title>PASS</title>')
1392      else:
1393        self.wfile.write('<title>page ' + str(i) + '</title>')
1394      self.wfile.write('page ' + str(i) + '<!-- ' + ('x' * 2048) + '-->')
1395
1396    self.wfile.write('--' + bound + '--')
1397    return True
1398
1399  def GetSSLSessionCacheHandler(self):
1400    """Send a reply containing a log of the session cache operations."""
1401
1402    if not self._ShouldHandleRequest('/ssl-session-cache'):
1403      return False
1404
1405    self.send_response(200)
1406    self.send_header('Content-Type', 'text/plain')
1407    self.end_headers()
1408    try:
1409      for (action, sessionID) in self.server.session_cache.log:
1410        self.wfile.write('%s\t%s\n' % (action, sessionID.encode('hex')))
1411    except AttributeError:
1412      self.wfile.write('Pass --https-record-resume in order to use' +
1413                       ' this request')
1414    return True
1415
1416  def SSLManySmallRecords(self):
1417    """Sends a reply consisting of a variety of small writes. These will be
1418    translated into a series of small SSL records when used over an HTTPS
1419    server."""
1420
1421    if not self._ShouldHandleRequest('/ssl-many-small-records'):
1422      return False
1423
1424    self.send_response(200)
1425    self.send_header('Content-Type', 'text/plain')
1426    self.end_headers()
1427
1428    # Write ~26K of data, in 1350 byte chunks
1429    for i in xrange(20):
1430      self.wfile.write('*' * 1350)
1431      self.wfile.flush()
1432    return True
1433
1434  def GetChannelID(self):
1435    """Send a reply containing the hashed ChannelID that the client provided."""
1436
1437    if not self._ShouldHandleRequest('/channel-id'):
1438      return False
1439
1440    self.send_response(200)
1441    self.send_header('Content-Type', 'text/plain')
1442    self.end_headers()
1443    channel_id = self.server.tlsConnection.channel_id.tostring()
1444    self.wfile.write(hashlib.sha256(channel_id).digest().encode('base64'))
1445    return True
1446
1447  def CloseSocketHandler(self):
1448    """Closes the socket without sending anything."""
1449
1450    if not self._ShouldHandleRequest('/close-socket'):
1451      return False
1452
1453    self.wfile.close()
1454    return True
1455
1456  def RangeResetHandler(self):
1457    """Send data broken up by connection resets every N (default 4K) bytes.
1458    Support range requests.  If the data requested doesn't straddle a reset
1459    boundary, it will all be sent.  Used for testing resuming downloads."""
1460
1461    def DataForRange(start, end):
1462      """Data to be provided for a particular range of bytes."""
1463      # Offset and scale to avoid too obvious (and hence potentially
1464      # collidable) data.
1465      return ''.join([chr(y % 256)
1466                      for y in range(start * 2 + 15, end * 2 + 15, 2)])
1467
1468    if not self._ShouldHandleRequest('/rangereset'):
1469      return False
1470
1471    _, _, url_path, _, query, _ = urlparse.urlparse(self.path)
1472
1473    # Defaults
1474    size = 8000
1475    # Note that the rst is sent just before sending the rst_boundary byte.
1476    rst_boundary = 4000
1477    respond_to_range = True
1478    hold_for_signal = False
1479    rst_limit = -1
1480    token = 'DEFAULT'
1481    fail_precondition = 0
1482    send_verifiers = True
1483
1484    # Parse the query
1485    qdict = urlparse.parse_qs(query, True)
1486    if 'size' in qdict:
1487      size = int(qdict['size'][0])
1488    if 'rst_boundary' in qdict:
1489      rst_boundary = int(qdict['rst_boundary'][0])
1490    if 'token' in qdict:
1491      # Identifying token for stateful tests.
1492      token = qdict['token'][0]
1493    if 'rst_limit' in qdict:
1494      # Max number of rsts for a given token.
1495      rst_limit = int(qdict['rst_limit'][0])
1496    if 'bounce_range' in qdict:
1497      respond_to_range = False
1498    if 'hold' in qdict:
1499      # Note that hold_for_signal will not work with null range requests;
1500      # see TODO below.
1501      hold_for_signal = True
1502    if 'no_verifiers' in qdict:
1503      send_verifiers = False
1504    if 'fail_precondition' in qdict:
1505      fail_precondition = int(qdict['fail_precondition'][0])
1506
1507    # Record already set information, or set it.
1508    rst_limit = TestPageHandler.rst_limits.setdefault(token, rst_limit)
1509    if rst_limit != 0:
1510      TestPageHandler.rst_limits[token] -= 1
1511    fail_precondition = TestPageHandler.fail_precondition.setdefault(
1512      token, fail_precondition)
1513    if fail_precondition != 0:
1514      TestPageHandler.fail_precondition[token] -= 1
1515
1516    first_byte = 0
1517    last_byte = size - 1
1518
1519    # Does that define what we want to return, or do we need to apply
1520    # a range?
1521    range_response = False
1522    range_header = self.headers.getheader('range')
1523    if range_header and respond_to_range:
1524      mo = re.match("bytes=(\d*)-(\d*)", range_header)
1525      if mo.group(1):
1526        first_byte = int(mo.group(1))
1527      if mo.group(2):
1528        last_byte = int(mo.group(2))
1529      if last_byte > size - 1:
1530        last_byte = size - 1
1531      range_response = True
1532      if last_byte < first_byte:
1533        return False
1534
1535    if (fail_precondition and
1536        (self.headers.getheader('If-Modified-Since') or
1537         self.headers.getheader('If-Match'))):
1538      self.send_response(412)
1539      self.end_headers()
1540      return True
1541
1542    if range_response:
1543      self.send_response(206)
1544      self.send_header('Content-Range',
1545                       'bytes %d-%d/%d' % (first_byte, last_byte, size))
1546    else:
1547      self.send_response(200)
1548    self.send_header('Content-Type', 'application/octet-stream')
1549    self.send_header('Content-Length', last_byte - first_byte + 1)
1550    if send_verifiers:
1551      self.send_header('Etag', '"XYZZY"')
1552      self.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
1553    self.end_headers()
1554
1555    if hold_for_signal:
1556      # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1557      # a single byte, the self.server.handle_request() below hangs
1558      # without processing new incoming requests.
1559      self.wfile.write(DataForRange(first_byte, first_byte + 1))
1560      first_byte = first_byte + 1
1561      # handle requests until one of them clears this flag.
1562      self.server.wait_for_download = True
1563      while self.server.wait_for_download:
1564        self.server.handle_request()
1565
1566    possible_rst = ((first_byte / rst_boundary) + 1) * rst_boundary
1567    if possible_rst >= last_byte or rst_limit == 0:
1568      # No RST has been requested in this range, so we don't need to
1569      # do anything fancy; just write the data and let the python
1570      # infrastructure close the connection.
1571      self.wfile.write(DataForRange(first_byte, last_byte + 1))
1572      self.wfile.flush()
1573      return True
1574
1575    # We're resetting the connection part way in; go to the RST
1576    # boundary and then send an RST.
1577    # Because socket semantics do not guarantee that all the data will be
1578    # sent when using the linger semantics to hard close a socket,
1579    # we send the data and then wait for our peer to release us
1580    # before sending the reset.
1581    data = DataForRange(first_byte, possible_rst)
1582    self.wfile.write(data)
1583    self.wfile.flush()
1584    self.server.wait_for_download = True
1585    while self.server.wait_for_download:
1586      self.server.handle_request()
1587    l_onoff = 1  # Linger is active.
1588    l_linger = 0  # Seconds to linger for.
1589    self.connection.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
1590                 struct.pack('ii', l_onoff, l_linger))
1591
1592    # Close all duplicates of the underlying socket to force the RST.
1593    self.wfile.close()
1594    self.rfile.close()
1595    self.connection.close()
1596
1597    return True
1598
1599  def DefaultResponseHandler(self):
1600    """This is the catch-all response handler for requests that aren't handled
1601    by one of the special handlers above.
1602    Note that we specify the content-length as without it the https connection
1603    is not closed properly (and the browser keeps expecting data)."""
1604
1605    contents = "Default response given for path: " + self.path
1606    self.send_response(200)
1607    self.send_header('Content-Type', 'text/html')
1608    self.send_header('Content-Length', len(contents))
1609    self.end_headers()
1610    if (self.command != 'HEAD'):
1611      self.wfile.write(contents)
1612    return True
1613
1614  def RedirectConnectHandler(self):
1615    """Sends a redirect to the CONNECT request for www.redirect.com. This
1616    response is not specified by the RFC, so the browser should not follow
1617    the redirect."""
1618
1619    if (self.path.find("www.redirect.com") < 0):
1620      return False
1621
1622    dest = "http://www.destination.com/foo.js"
1623
1624    self.send_response(302)  # moved temporarily
1625    self.send_header('Location', dest)
1626    self.send_header('Connection', 'close')
1627    self.end_headers()
1628    return True
1629
1630  def ServerAuthConnectHandler(self):
1631    """Sends a 401 to the CONNECT request for www.server-auth.com. This
1632    response doesn't make sense because the proxy server cannot request
1633    server authentication."""
1634
1635    if (self.path.find("www.server-auth.com") < 0):
1636      return False
1637
1638    challenge = 'Basic realm="WallyWorld"'
1639
1640    self.send_response(401)  # unauthorized
1641    self.send_header('WWW-Authenticate', challenge)
1642    self.send_header('Connection', 'close')
1643    self.end_headers()
1644    return True
1645
1646  def DefaultConnectResponseHandler(self):
1647    """This is the catch-all response handler for CONNECT requests that aren't
1648    handled by one of the special handlers above.  Real Web servers respond
1649    with 400 to CONNECT requests."""
1650
1651    contents = "Your client has issued a malformed or illegal request."
1652    self.send_response(400)  # bad request
1653    self.send_header('Content-Type', 'text/html')
1654    self.send_header('Content-Length', len(contents))
1655    self.end_headers()
1656    self.wfile.write(contents)
1657    return True
1658
1659  # called by the redirect handling function when there is no parameter
1660  def sendRedirectHelp(self, redirect_name):
1661    self.send_response(200)
1662    self.send_header('Content-Type', 'text/html')
1663    self.end_headers()
1664    self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
1665    self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
1666    self.wfile.write('</body></html>')
1667
1668  # called by chunked handling function
1669  def sendChunkHelp(self, chunk):
1670    # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1671    self.wfile.write('%X\r\n' % len(chunk))
1672    self.wfile.write(chunk)
1673    self.wfile.write('\r\n')
1674
1675
1676class OCSPHandler(testserver_base.BasePageHandler):
1677  def __init__(self, request, client_address, socket_server):
1678    handlers = [self.OCSPResponse]
1679    self.ocsp_response = socket_server.ocsp_response
1680    testserver_base.BasePageHandler.__init__(self, request, client_address,
1681                                             socket_server, [], handlers, [],
1682                                             handlers, [])
1683
1684  def OCSPResponse(self):
1685    self.send_response(200)
1686    self.send_header('Content-Type', 'application/ocsp-response')
1687    self.send_header('Content-Length', str(len(self.ocsp_response)))
1688    self.end_headers()
1689
1690    self.wfile.write(self.ocsp_response)
1691
1692
1693class TCPEchoHandler(SocketServer.BaseRequestHandler):
1694  """The RequestHandler class for TCP echo server.
1695
1696  It is instantiated once per connection to the server, and overrides the
1697  handle() method to implement communication to the client.
1698  """
1699
1700  def handle(self):
1701    """Handles the request from the client and constructs a response."""
1702
1703    data = self.request.recv(65536).strip()
1704    # Verify the "echo request" message received from the client. Send back
1705    # "echo response" message if "echo request" message is valid.
1706    try:
1707      return_data = echo_message.GetEchoResponseData(data)
1708      if not return_data:
1709        return
1710    except ValueError:
1711      return
1712
1713    self.request.send(return_data)
1714
1715
1716class UDPEchoHandler(SocketServer.BaseRequestHandler):
1717  """The RequestHandler class for UDP echo server.
1718
1719  It is instantiated once per connection to the server, and overrides the
1720  handle() method to implement communication to the client.
1721  """
1722
1723  def handle(self):
1724    """Handles the request from the client and constructs a response."""
1725
1726    data = self.request[0].strip()
1727    request_socket = self.request[1]
1728    # Verify the "echo request" message received from the client. Send back
1729    # "echo response" message if "echo request" message is valid.
1730    try:
1731      return_data = echo_message.GetEchoResponseData(data)
1732      if not return_data:
1733        return
1734    except ValueError:
1735      return
1736    request_socket.sendto(return_data, self.client_address)
1737
1738
1739class BasicAuthProxyRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1740  """A request handler that behaves as a proxy server which requires
1741  basic authentication. Only CONNECT, GET and HEAD is supported for now.
1742  """
1743
1744  _AUTH_CREDENTIAL = 'Basic Zm9vOmJhcg==' # foo:bar
1745
1746  def parse_request(self):
1747    """Overrides parse_request to check credential."""
1748
1749    if not BaseHTTPServer.BaseHTTPRequestHandler.parse_request(self):
1750      return False
1751
1752    auth = self.headers.getheader('Proxy-Authorization')
1753    if auth != self._AUTH_CREDENTIAL:
1754      self.send_response(407)
1755      self.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1756      self.end_headers()
1757      return False
1758
1759    return True
1760
1761  def _start_read_write(self, sock):
1762    sock.setblocking(0)
1763    self.request.setblocking(0)
1764    rlist = [self.request, sock]
1765    while True:
1766      ready_sockets, _unused, errors = select.select(rlist, [], [])
1767      if errors:
1768        self.send_response(500)
1769        self.end_headers()
1770        return
1771      for s in ready_sockets:
1772        received = s.recv(1024)
1773        if len(received) == 0:
1774          return
1775        if s == self.request:
1776          other = sock
1777        else:
1778          other = self.request
1779        other.send(received)
1780
1781  def _do_common_method(self):
1782    url = urlparse.urlparse(self.path)
1783    port = url.port
1784    if not port:
1785      if url.scheme == 'http':
1786        port = 80
1787      elif url.scheme == 'https':
1788        port = 443
1789    if not url.hostname or not port:
1790      self.send_response(400)
1791      self.end_headers()
1792      return
1793
1794    if len(url.path) == 0:
1795      path = '/'
1796    else:
1797      path = url.path
1798    if len(url.query) > 0:
1799      path = '%s?%s' % (url.path, url.query)
1800
1801    sock = None
1802    try:
1803      sock = socket.create_connection((url.hostname, port))
1804      sock.send('%s %s %s\r\n' % (
1805          self.command, path, self.protocol_version))
1806      for header in self.headers.headers:
1807        header = header.strip()
1808        if (header.lower().startswith('connection') or
1809            header.lower().startswith('proxy')):
1810          continue
1811        sock.send('%s\r\n' % header)
1812      sock.send('\r\n')
1813      self._start_read_write(sock)
1814    except Exception:
1815      self.send_response(500)
1816      self.end_headers()
1817    finally:
1818      if sock is not None:
1819        sock.close()
1820
1821  def do_CONNECT(self):
1822    try:
1823      pos = self.path.rfind(':')
1824      host = self.path[:pos]
1825      port = int(self.path[pos+1:])
1826    except Exception:
1827      self.send_response(400)
1828      self.end_headers()
1829
1830    try:
1831      sock = socket.create_connection((host, port))
1832      self.send_response(200, 'Connection established')
1833      self.end_headers()
1834      self._start_read_write(sock)
1835    except Exception:
1836      self.send_response(500)
1837      self.end_headers()
1838    finally:
1839      sock.close()
1840
1841  def do_GET(self):
1842    self._do_common_method()
1843
1844  def do_HEAD(self):
1845    self._do_common_method()
1846
1847
1848class ServerRunner(testserver_base.TestServerRunner):
1849  """TestServerRunner for the net test servers."""
1850
1851  def __init__(self):
1852    super(ServerRunner, self).__init__()
1853    self.__ocsp_server = None
1854
1855  def __make_data_dir(self):
1856    if self.options.data_dir:
1857      if not os.path.isdir(self.options.data_dir):
1858        raise testserver_base.OptionError('specified data dir not found: ' +
1859            self.options.data_dir + ' exiting...')
1860      my_data_dir = self.options.data_dir
1861    else:
1862      # Create the default path to our data dir, relative to the exe dir.
1863      my_data_dir = os.path.join(BASE_DIR, "..", "..", "..", "..",
1864                                 "test", "data")
1865
1866      #TODO(ibrar): Must use Find* funtion defined in google\tools
1867      #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1868
1869    return my_data_dir
1870
1871  def create_server(self, server_data):
1872    port = self.options.port
1873    host = self.options.host
1874
1875    if self.options.server_type == SERVER_HTTP:
1876      if self.options.https:
1877        pem_cert_and_key = None
1878        if self.options.cert_and_key_file:
1879          if not os.path.isfile(self.options.cert_and_key_file):
1880            raise testserver_base.OptionError(
1881                'specified server cert file not found: ' +
1882                self.options.cert_and_key_file + ' exiting...')
1883          pem_cert_and_key = file(self.options.cert_and_key_file, 'r').read()
1884        else:
1885          # generate a new certificate and run an OCSP server for it.
1886          self.__ocsp_server = OCSPServer((host, 0), OCSPHandler)
1887          print ('OCSP server started on %s:%d...' %
1888              (host, self.__ocsp_server.server_port))
1889
1890          ocsp_der = None
1891          ocsp_state = None
1892
1893          if self.options.ocsp == 'ok':
1894            ocsp_state = minica.OCSP_STATE_GOOD
1895          elif self.options.ocsp == 'revoked':
1896            ocsp_state = minica.OCSP_STATE_REVOKED
1897          elif self.options.ocsp == 'invalid':
1898            ocsp_state = minica.OCSP_STATE_INVALID
1899          elif self.options.ocsp == 'unauthorized':
1900            ocsp_state = minica.OCSP_STATE_UNAUTHORIZED
1901          elif self.options.ocsp == 'unknown':
1902            ocsp_state = minica.OCSP_STATE_UNKNOWN
1903          else:
1904            raise testserver_base.OptionError('unknown OCSP status: ' +
1905                self.options.ocsp_status)
1906
1907          (pem_cert_and_key, ocsp_der) = minica.GenerateCertKeyAndOCSP(
1908              subject = "127.0.0.1",
1909              ocsp_url = ("http://%s:%d/ocsp" %
1910                  (host, self.__ocsp_server.server_port)),
1911              ocsp_state = ocsp_state)
1912
1913          self.__ocsp_server.ocsp_response = ocsp_der
1914
1915        for ca_cert in self.options.ssl_client_ca:
1916          if not os.path.isfile(ca_cert):
1917            raise testserver_base.OptionError(
1918                'specified trusted client CA file not found: ' + ca_cert +
1919                ' exiting...')
1920        server = HTTPSServer((host, port), TestPageHandler, pem_cert_and_key,
1921                             self.options.ssl_client_auth,
1922                             self.options.ssl_client_ca,
1923                             self.options.ssl_bulk_cipher,
1924                             self.options.record_resume,
1925                             self.options.tls_intolerant)
1926        print 'HTTPS server started on %s:%d...' % (host, server.server_port)
1927      else:
1928        server = HTTPServer((host, port), TestPageHandler)
1929        print 'HTTP server started on %s:%d...' % (host, server.server_port)
1930
1931      server.data_dir = self.__make_data_dir()
1932      server.file_root_url = self.options.file_root_url
1933      server_data['port'] = server.server_port
1934    elif self.options.server_type == SERVER_WEBSOCKET:
1935      # Launch pywebsocket via WebSocketServer.
1936      logger = logging.getLogger()
1937      logger.addHandler(logging.StreamHandler())
1938      # TODO(toyoshim): Remove following os.chdir. Currently this operation
1939      # is required to work correctly. It should be fixed from pywebsocket side.
1940      os.chdir(self.__make_data_dir())
1941      websocket_options = WebSocketOptions(host, port, '.')
1942      if self.options.cert_and_key_file:
1943        websocket_options.use_tls = True
1944        websocket_options.private_key = self.options.cert_and_key_file
1945        websocket_options.certificate = self.options.cert_and_key_file
1946      if self.options.ssl_client_auth:
1947        websocket_options.tls_client_auth = True
1948        if len(self.options.ssl_client_ca) != 1:
1949          raise testserver_base.OptionError(
1950              'one trusted client CA file should be specified')
1951        if not os.path.isfile(self.options.ssl_client_ca[0]):
1952          raise testserver_base.OptionError(
1953              'specified trusted client CA file not found: ' +
1954              self.options.ssl_client_ca[0] + ' exiting...')
1955        websocket_options.tls_client_ca = self.options.ssl_client_ca[0]
1956      server = WebSocketServer(websocket_options)
1957      print 'WebSocket server started on %s:%d...' % (host, server.server_port)
1958      server_data['port'] = server.server_port
1959    elif self.options.server_type == SERVER_TCP_ECHO:
1960      # Used for generating the key (randomly) that encodes the "echo request"
1961      # message.
1962      random.seed()
1963      server = TCPEchoServer((host, port), TCPEchoHandler)
1964      print 'Echo TCP server started on port %d...' % server.server_port
1965      server_data['port'] = server.server_port
1966    elif self.options.server_type == SERVER_UDP_ECHO:
1967      # Used for generating the key (randomly) that encodes the "echo request"
1968      # message.
1969      random.seed()
1970      server = UDPEchoServer((host, port), UDPEchoHandler)
1971      print 'Echo UDP server started on port %d...' % server.server_port
1972      server_data['port'] = server.server_port
1973    elif self.options.server_type == SERVER_BASIC_AUTH_PROXY:
1974      server = HTTPServer((host, port), BasicAuthProxyRequestHandler)
1975      print 'BasicAuthProxy server started on port %d...' % server.server_port
1976      server_data['port'] = server.server_port
1977    elif self.options.server_type == SERVER_FTP:
1978      my_data_dir = self.__make_data_dir()
1979
1980      # Instantiate a dummy authorizer for managing 'virtual' users
1981      authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
1982
1983      # Define a new user having full r/w permissions and a read-only
1984      # anonymous user
1985      authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
1986
1987      authorizer.add_anonymous(my_data_dir)
1988
1989      # Instantiate FTP handler class
1990      ftp_handler = pyftpdlib.ftpserver.FTPHandler
1991      ftp_handler.authorizer = authorizer
1992
1993      # Define a customized banner (string returned when client connects)
1994      ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
1995                            pyftpdlib.ftpserver.__ver__)
1996
1997      # Instantiate FTP server class and listen to address:port
1998      server = pyftpdlib.ftpserver.FTPServer((host, port), ftp_handler)
1999      server_data['port'] = server.socket.getsockname()[1]
2000      print 'FTP server started on port %d...' % server_data['port']
2001    else:
2002      raise testserver_base.OptionError('unknown server type' +
2003          self.options.server_type)
2004
2005    return server
2006
2007  def run_server(self):
2008    if self.__ocsp_server:
2009      self.__ocsp_server.serve_forever_on_thread()
2010
2011    testserver_base.TestServerRunner.run_server(self)
2012
2013    if self.__ocsp_server:
2014      self.__ocsp_server.stop_serving()
2015
2016  def add_options(self):
2017    testserver_base.TestServerRunner.add_options(self)
2018    self.option_parser.add_option('-f', '--ftp', action='store_const',
2019                                  const=SERVER_FTP, default=SERVER_HTTP,
2020                                  dest='server_type',
2021                                  help='start up an FTP server.')
2022    self.option_parser.add_option('--tcp-echo', action='store_const',
2023                                  const=SERVER_TCP_ECHO, default=SERVER_HTTP,
2024                                  dest='server_type',
2025                                  help='start up a tcp echo server.')
2026    self.option_parser.add_option('--udp-echo', action='store_const',
2027                                  const=SERVER_UDP_ECHO, default=SERVER_HTTP,
2028                                  dest='server_type',
2029                                  help='start up a udp echo server.')
2030    self.option_parser.add_option('--basic-auth-proxy', action='store_const',
2031                                  const=SERVER_BASIC_AUTH_PROXY,
2032                                  default=SERVER_HTTP, dest='server_type',
2033                                  help='start up a proxy server which requires '
2034                                  'basic authentication.')
2035    self.option_parser.add_option('--websocket', action='store_const',
2036                                  const=SERVER_WEBSOCKET, default=SERVER_HTTP,
2037                                  dest='server_type',
2038                                  help='start up a WebSocket server.')
2039    self.option_parser.add_option('--https', action='store_true',
2040                                  dest='https', help='Specify that https '
2041                                  'should be used.')
2042    self.option_parser.add_option('--cert-and-key-file',
2043                                  dest='cert_and_key_file', help='specify the '
2044                                  'path to the file containing the certificate '
2045                                  'and private key for the server in PEM '
2046                                  'format')
2047    self.option_parser.add_option('--ocsp', dest='ocsp', default='ok',
2048                                  help='The type of OCSP response generated '
2049                                  'for the automatically generated '
2050                                  'certificate. One of [ok,revoked,invalid]')
2051    self.option_parser.add_option('--tls-intolerant', dest='tls_intolerant',
2052                                  default='0', type='int',
2053                                  help='If nonzero, certain TLS connections '
2054                                  'will be aborted in order to test version '
2055                                  'fallback. 1 means all TLS versions will be '
2056                                  'aborted. 2 means TLS 1.1 or higher will be '
2057                                  'aborted. 3 means TLS 1.2 or higher will be '
2058                                  'aborted.')
2059    self.option_parser.add_option('--https-record-resume',
2060                                  dest='record_resume', const=True,
2061                                  default=False, action='store_const',
2062                                  help='Record resumption cache events rather '
2063                                  'than resuming as normal. Allows the use of '
2064                                  'the /ssl-session-cache request')
2065    self.option_parser.add_option('--ssl-client-auth', action='store_true',
2066                                  help='Require SSL client auth on every '
2067                                  'connection.')
2068    self.option_parser.add_option('--ssl-client-ca', action='append',
2069                                  default=[], help='Specify that the client '
2070                                  'certificate request should include the CA '
2071                                  'named in the subject of the DER-encoded '
2072                                  'certificate contained in the specified '
2073                                  'file. This option may appear multiple '
2074                                  'times, indicating multiple CA names should '
2075                                  'be sent in the request.')
2076    self.option_parser.add_option('--ssl-bulk-cipher', action='append',
2077                                  help='Specify the bulk encryption '
2078                                  'algorithm(s) that will be accepted by the '
2079                                  'SSL server. Valid values are "aes256", '
2080                                  '"aes128", "3des", "rc4". If omitted, all '
2081                                  'algorithms will be used. This option may '
2082                                  'appear multiple times, indicating '
2083                                  'multiple algorithms should be enabled.');
2084    self.option_parser.add_option('--file-root-url', default='/files/',
2085                                  help='Specify a root URL for files served.')
2086
2087
2088if __name__ == '__main__':
2089  sys.exit(ServerRunner().main())
2090