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