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