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