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