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