Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / net / tools / testserver / testserver.py
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
7 testing Chrome.
8
9 It supports several test URLs, as specified by the handlers in TestPageHandler.
10 By default, it listens on an ephemeral port and sends the port number back to
11 the originating process over a pipe. The originating process can specify an
12 explicit port if necessary.
13 It can use https if you specify the flag --https=CERT where CERT is the path
14 to a pem file containing the certificate and private key that should be used.
15 """
16
17 import base64
18 import BaseHTTPServer
19 import cgi
20 import hashlib
21 import logging
22 import minica
23 import os
24 import json
25 import random
26 import re
27 import select
28 import socket
29 import SocketServer
30 import ssl
31 import struct
32 import sys
33 import threading
34 import time
35 import urllib
36 import urlparse
37 import zlib
38
39 BASE_DIR = os.path.dirname(os.path.abspath(__file__))
40 ROOT_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.
46 try:
47   os.remove(os.path.join(ROOT_DIR, 'third_party', 'tlslite',
48                          'tlslite', 'utils', 'hmac.pyc'))
49 except Exception:
50   pass
51
52 # Append at the end of sys.path, it's fine to use the system library.
53 sys.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.
57 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'pywebsocket', 'src'))
58 sys.path.insert(0, os.path.join(ROOT_DIR, 'third_party', 'tlslite'))
59
60 import mod_pywebsocket.standalone
61 from mod_pywebsocket.standalone import WebSocketServer
62 # import manually
63 mod_pywebsocket.standalone.ssl = ssl
64
65 import pyftpdlib.ftpserver
66
67 import tlslite
68 import tlslite.api
69
70 import echo_message
71 import testserver_base
72
73 SERVER_HTTP = 0
74 SERVER_FTP = 1
75 SERVER_TCP_ECHO = 2
76 SERVER_UDP_ECHO = 3
77 SERVER_BASIC_AUTH_PROXY = 4
78 SERVER_WEBSOCKET = 5
79
80 # Default request queue size for WebSocketServer.
81 _DEFAULT_REQUEST_QUEUE_SIZE = 128
82
83 class 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
109 class 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
124 class 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
132 class 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
148 class 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
244 class FTPServer(testserver_base.ClientRestrictingServerMixIn,
245                 pyftpdlib.ftpserver.FTPServer):
246   """This is a specialization of FTPServer that adds client verification."""
247
248   pass
249
250
251 class 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
271 class 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
291 class 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
1729 class 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
1746 class 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
1769 class 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
1792 class 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
1901 class 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
2226 if __name__ == '__main__':
2227   sys.exit(ServerRunner().main())