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.
6 """This is a simple HTTP/FTP/TCP/UDP/BASIC_AUTH_PROXY/WEBSOCKET server used for
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.
39 BASE_DIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
40 ROOT_DIR
= os
.path
.dirname(os
.path
.dirname(os
.path
.dirname(BASE_DIR
)))
42 # Temporary hack to deal with tlslite 0.3.8 -> 0.4.6 upgrade.
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.
47 os
.remove(os
.path
.join(ROOT_DIR
, 'third_party', 'tlslite',
48 'tlslite', 'utils', 'hmac.pyc'))
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'))
55 # Insert at the beginning of the path, we want to use our copies of the library
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'))
60 import mod_pywebsocket
.standalone
61 from mod_pywebsocket
.standalone
import WebSocketServer
63 mod_pywebsocket
.standalone
.ssl
= ssl
65 import pyftpdlib
.ftpserver
71 import testserver_base
77 SERVER_BASIC_AUTH_PROXY
= 4
80 # Default request queue size for WebSocketServer.
81 _DEFAULT_REQUEST_QUEUE_SIZE
= 128
83 class WebSocketOptions
:
84 """Holds options for WebSocketServer."""
86 def __init__(self
, host
, port
, data_dir
):
87 self
.request_queue_size
= _DEFAULT_REQUEST_QUEUE_SIZE
88 self
.server_host
= host
90 self
.websock_handlers
= data_dir
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
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')
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."""
116 def __getitem__(self
, sessionID
):
117 self
.log
.append(('lookup', sessionID
))
120 def __setitem__(self
, sessionID
, session
):
121 self
.log
.append(('insert', sessionID
))
124 class HTTPServer(testserver_base
.ClientRestrictingServerMixIn
,
125 testserver_base
.BrokenPipeHandlerMixIn
,
126 testserver_base
.StoppableHTTPServer
):
127 """This is a specialization of StoppableHTTPServer that adds client
132 class OCSPServer(testserver_base
.ClientRestrictingServerMixIn
,
133 testserver_base
.BrokenPipeHandlerMixIn
,
134 BaseHTTPServer
.HTTPServer
):
135 """This is a specialization of HTTPServer that serves an
138 def serve_forever_on_thread(self
):
139 self
.thread
= threading
.Thread(target
= self
.serve_forever
,
140 name
= "OCSPServerThread")
143 def stop_serving(self
):
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."""
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
,
161 alert_after_handshake
):
162 self
.cert_chain
= tlslite
.api
.X509CertChain()
163 self
.cert_chain
.parsePemList(pem_cert_and_key
)
164 # Force using only python implementation - otherwise behavior is different
165 # depending on whether m2crypto Python module is present (error is thrown
166 # when it is). m2crypto uses a C (based on OpenSSL) implementation under
168 self
.private_key
= tlslite
.api
.parsePEMKey(pem_cert_and_key
,
170 implementations
=['python'])
171 self
.ssl_client_auth
= ssl_client_auth
172 self
.ssl_client_cas
= []
173 self
.ssl_client_cert_types
= []
175 self
.next_protos
= ['http/1.1']
177 self
.next_protos
= None
178 self
.signed_cert_timestamps
= signed_cert_timestamps
179 self
.fallback_scsv_enabled
= fallback_scsv_enabled
180 self
.ocsp_response
= ocsp_response
183 for ca_file
in ssl_client_cas
:
184 s
= open(ca_file
).read()
185 x509
= tlslite
.api
.X509()
187 self
.ssl_client_cas
.append(x509
.subject
)
189 for cert_type
in ssl_client_cert_types
:
190 self
.ssl_client_cert_types
.append({
191 "rsa_sign": tlslite
.api
.ClientCertificateType
.rsa_sign
,
192 "ecdsa_sign": tlslite
.api
.ClientCertificateType
.ecdsa_sign
,
195 self
.ssl_handshake_settings
= tlslite
.api
.HandshakeSettings()
196 # Enable SSLv3 for testing purposes.
197 self
.ssl_handshake_settings
.minVersion
= (3, 0)
198 if ssl_bulk_ciphers
is not None:
199 self
.ssl_handshake_settings
.cipherNames
= ssl_bulk_ciphers
200 if ssl_key_exchanges
is not None:
201 self
.ssl_handshake_settings
.keyExchangeNames
= ssl_key_exchanges
202 if tls_intolerant
!= 0:
203 self
.ssl_handshake_settings
.tlsIntolerant
= (3, tls_intolerant
)
204 self
.ssl_handshake_settings
.tlsIntoleranceType
= tls_intolerance_type
205 if alert_after_handshake
:
206 self
.ssl_handshake_settings
.alertAfterHandshake
= True
208 if record_resume_info
:
209 # If record_resume_info is true then we'll replace the session cache with
210 # an object that records the lookups and inserts that it sees.
211 self
.session_cache
= RecordingSSLSessionCache()
213 self
.session_cache
= tlslite
.api
.SessionCache()
214 testserver_base
.StoppableHTTPServer
.__init
__(self
,
216 request_hander_class
)
218 def handshake(self
, tlsConnection
):
219 """Creates the SSL connection."""
222 self
.tlsConnection
= tlsConnection
223 tlsConnection
.handshakeServer(certChain
=self
.cert_chain
,
224 privateKey
=self
.private_key
,
225 sessionCache
=self
.session_cache
,
226 reqCert
=self
.ssl_client_auth
,
227 settings
=self
.ssl_handshake_settings
,
228 reqCAs
=self
.ssl_client_cas
,
229 reqCertTypes
=self
.ssl_client_cert_types
,
230 nextProtos
=self
.next_protos
,
231 signedCertTimestamps
=
232 self
.signed_cert_timestamps
,
233 fallbackSCSV
=self
.fallback_scsv_enabled
,
234 ocspResponse
= self
.ocsp_response
)
235 tlsConnection
.ignoreAbruptClose
= True
237 except tlslite
.api
.TLSAbruptCloseError
:
238 # Ignore abrupt close.
240 except tlslite
.api
.TLSError
, error
:
241 print "Handshake failure:", str(error
)
245 class FTPServer(testserver_base
.ClientRestrictingServerMixIn
,
246 pyftpdlib
.ftpserver
.FTPServer
):
247 """This is a specialization of FTPServer that adds client verification."""
252 class TCPEchoServer(testserver_base
.ClientRestrictingServerMixIn
,
253 SocketServer
.TCPServer
):
254 """A TCP echo server that echoes back what it has received."""
256 def server_bind(self
):
257 """Override server_bind to store the server name."""
259 SocketServer
.TCPServer
.server_bind(self
)
260 host
, port
= self
.socket
.getsockname()[:2]
261 self
.server_name
= socket
.getfqdn(host
)
262 self
.server_port
= port
264 def serve_forever(self
):
266 self
.nonce_time
= None
268 self
.handle_request()
272 class UDPEchoServer(testserver_base
.ClientRestrictingServerMixIn
,
273 SocketServer
.UDPServer
):
274 """A UDP echo server that echoes back what it has received."""
276 def server_bind(self
):
277 """Override server_bind to store the server name."""
279 SocketServer
.UDPServer
.server_bind(self
)
280 host
, port
= self
.socket
.getsockname()[:2]
281 self
.server_name
= socket
.getfqdn(host
)
282 self
.server_port
= port
284 def serve_forever(self
):
286 self
.nonce_time
= None
288 self
.handle_request()
292 class TestPageHandler(testserver_base
.BasePageHandler
):
293 # Class variables to allow for persistence state between page handler
296 fail_precondition
= {}
298 def __init__(self
, request
, client_address
, socket_server
):
300 self
.RedirectConnectHandler
,
301 self
.ServerAuthConnectHandler
,
302 self
.DefaultConnectResponseHandler
]
304 self
.NoCacheMaxAgeTimeHandler
,
305 self
.NoCacheTimeHandler
,
306 self
.CacheTimeHandler
,
307 self
.CacheExpiresHandler
,
308 self
.CacheProxyRevalidateHandler
,
309 self
.CachePrivateHandler
,
310 self
.CachePublicHandler
,
311 self
.CacheSMaxAgeHandler
,
312 self
.CacheMustRevalidateHandler
,
313 self
.CacheMustRevalidateMaxAgeHandler
,
314 self
.CacheNoStoreHandler
,
315 self
.CacheNoStoreMaxAgeHandler
,
316 self
.CacheNoTransformHandler
,
317 self
.DownloadHandler
,
318 self
.DownloadFinishHandler
,
320 self
.EchoHeaderCache
,
324 self
.SetCookieHandler
,
325 self
.SetManyCookiesHandler
,
326 self
.ExpectAndSetCookieHandler
,
327 self
.SetHeaderHandler
,
328 self
.AuthBasicHandler
,
329 self
.AuthDigestHandler
,
330 self
.SlowServerHandler
,
331 self
.ChunkedServerHandler
,
332 self
.NoContentHandler
,
333 self
.ServerRedirectHandler
,
334 self
.CrossSiteRedirectHandler
,
335 self
.ClientRedirectHandler
,
336 self
.GetSSLSessionCacheHandler
,
337 self
.SSLManySmallRecords
,
340 self
.ClientCipherListHandler
,
341 self
.CloseSocketHandler
,
342 self
.RangeResetHandler
,
343 self
.DefaultResponseHandler
]
345 self
.EchoTitleHandler
,
347 self
.PostOnlyFileHandler
,
348 self
.EchoMultipartPostHandler
] + get_handlers
350 self
.EchoTitleHandler
,
351 self
.EchoHandler
] + get_handlers
354 self
.DefaultResponseHandler
]
357 'crx' : 'application/x-chrome-extension',
358 'exe' : 'application/octet-stream',
360 'jpeg' : 'image/jpeg',
361 'jpg' : 'image/jpeg',
362 'js' : 'application/javascript',
363 'json': 'application/json',
364 'pdf' : 'application/pdf',
365 'txt' : 'text/plain',
369 self
._default
_mime
_type
= 'text/html'
371 testserver_base
.BasePageHandler
.__init
__(self
, request
, client_address
,
372 socket_server
, connect_handlers
,
373 get_handlers
, head_handlers
,
374 post_handlers
, put_handlers
)
376 def GetMIMETypeFromName(self
, file_name
):
377 """Returns the mime type for the specified file_name. So far it only looks
378 at the file extension."""
380 (_shortname
, extension
) = os
.path
.splitext(file_name
.split("?")[0])
381 if len(extension
) == 0:
383 return self
._default
_mime
_type
385 # extension starts with a dot, so we need to remove it
386 return self
._mime
_types
.get(extension
[1:], self
._default
_mime
_type
)
388 def NoCacheMaxAgeTimeHandler(self
):
389 """This request handler yields a page with the title set to the current
390 system time, and no caching requested."""
392 if not self
._ShouldHandleRequest
("/nocachetime/maxage"):
395 self
.send_response(200)
396 self
.send_header('Cache-Control', 'max-age=0')
397 self
.send_header('Content-Type', 'text/html')
400 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
405 def NoCacheTimeHandler(self
):
406 """This request handler yields a page with the title set to the current
407 system time, and no caching requested."""
409 if not self
._ShouldHandleRequest
("/nocachetime"):
412 self
.send_response(200)
413 self
.send_header('Cache-Control', 'no-cache')
414 self
.send_header('Content-Type', 'text/html')
417 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
422 def CacheTimeHandler(self
):
423 """This request handler yields a page with the title set to the current
424 system time, and allows caching for one minute."""
426 if not self
._ShouldHandleRequest
("/cachetime"):
429 self
.send_response(200)
430 self
.send_header('Cache-Control', 'max-age=60')
431 self
.send_header('Content-Type', 'text/html')
434 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
439 def CacheExpiresHandler(self
):
440 """This request handler yields a page with the title set to the current
441 system time, and set the page to expire on 1 Jan 2099."""
443 if not self
._ShouldHandleRequest
("/cache/expires"):
446 self
.send_response(200)
447 self
.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
448 self
.send_header('Content-Type', 'text/html')
451 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
456 def CacheProxyRevalidateHandler(self
):
457 """This request handler yields a page with the title set to the current
458 system time, and allows caching for 60 seconds"""
460 if not self
._ShouldHandleRequest
("/cache/proxy-revalidate"):
463 self
.send_response(200)
464 self
.send_header('Content-Type', 'text/html')
465 self
.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
468 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
473 def CachePrivateHandler(self
):
474 """This request handler yields a page with the title set to the current
475 system time, and allows caching for 3 seconds."""
477 if not self
._ShouldHandleRequest
("/cache/private"):
480 self
.send_response(200)
481 self
.send_header('Content-Type', 'text/html')
482 self
.send_header('Cache-Control', 'max-age=3, private')
485 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
490 def CachePublicHandler(self
):
491 """This request handler yields a page with the title set to the current
492 system time, and allows caching for 3 seconds."""
494 if not self
._ShouldHandleRequest
("/cache/public"):
497 self
.send_response(200)
498 self
.send_header('Content-Type', 'text/html')
499 self
.send_header('Cache-Control', 'max-age=3, public')
502 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
507 def CacheSMaxAgeHandler(self
):
508 """This request handler yields a page with the title set to the current
509 system time, and does not allow for caching."""
511 if not self
._ShouldHandleRequest
("/cache/s-maxage"):
514 self
.send_response(200)
515 self
.send_header('Content-Type', 'text/html')
516 self
.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
519 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
524 def CacheMustRevalidateHandler(self
):
525 """This request handler yields a page with the title set to the current
526 system time, and does not allow caching."""
528 if not self
._ShouldHandleRequest
("/cache/must-revalidate"):
531 self
.send_response(200)
532 self
.send_header('Content-Type', 'text/html')
533 self
.send_header('Cache-Control', 'must-revalidate')
536 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
541 def CacheMustRevalidateMaxAgeHandler(self
):
542 """This request handler yields a page with the title set to the current
543 system time, and does not allow caching event though max-age of 60
544 seconds is specified."""
546 if not self
._ShouldHandleRequest
("/cache/must-revalidate/max-age"):
549 self
.send_response(200)
550 self
.send_header('Content-Type', 'text/html')
551 self
.send_header('Cache-Control', 'max-age=60, must-revalidate')
554 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
559 def CacheNoStoreHandler(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."""
563 if not self
._ShouldHandleRequest
("/cache/no-store"):
566 self
.send_response(200)
567 self
.send_header('Content-Type', 'text/html')
568 self
.send_header('Cache-Control', 'no-store')
571 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
576 def CacheNoStoreMaxAgeHandler(self
):
577 """This request handler yields a page with the title set to the current
578 system time, and does not allow the page to be stored even though max-age
579 of 60 seconds is specified."""
581 if not self
._ShouldHandleRequest
("/cache/no-store/max-age"):
584 self
.send_response(200)
585 self
.send_header('Content-Type', 'text/html')
586 self
.send_header('Cache-Control', 'max-age=60, no-store')
589 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
595 def CacheNoTransformHandler(self
):
596 """This request handler yields a page with the title set to the current
597 system time, and does not allow the content to transformed during
598 user-agent caching"""
600 if not self
._ShouldHandleRequest
("/cache/no-transform"):
603 self
.send_response(200)
604 self
.send_header('Content-Type', 'text/html')
605 self
.send_header('Cache-Control', 'no-transform')
608 self
.wfile
.write('<html><head><title>%s</title></head></html>' %
613 def EchoHeader(self
):
614 """This handler echoes back the value of a specific request header."""
616 return self
.EchoHeaderHelper("/echoheader")
618 def EchoHeaderCache(self
):
619 """This function echoes back the value of a specific request header while
620 allowing caching for 16 hours."""
622 return self
.EchoHeaderHelper("/echoheadercache")
624 def EchoHeaderHelper(self
, echo_header
):
625 """This function echoes back the value of the request header passed in."""
627 if not self
._ShouldHandleRequest
(echo_header
):
630 query_char
= self
.path
.find('?')
632 header_name
= self
.path
[query_char
+1:]
634 self
.send_response(200)
635 self
.send_header('Content-Type', 'text/plain')
636 if echo_header
== '/echoheadercache':
637 self
.send_header('Cache-control', 'max-age=60000')
639 self
.send_header('Cache-control', 'no-cache')
640 # insert a vary header to properly indicate that the cachability of this
641 # request is subject to value of the request header being echoed.
642 if len(header_name
) > 0:
643 self
.send_header('Vary', header_name
)
646 if len(header_name
) > 0:
647 self
.wfile
.write(self
.headers
.getheader(header_name
))
651 def ReadRequestBody(self
):
652 """This function reads the body of the current HTTP request, handling
653 both plain and chunked transfer encoded requests."""
655 if self
.headers
.getheader('transfer-encoding') != 'chunked':
656 length
= int(self
.headers
.getheader('content-length'))
657 return self
.rfile
.read(length
)
659 # Read the request body as chunks.
662 line
= self
.rfile
.readline()
663 length
= int(line
, 16)
665 self
.rfile
.readline()
667 body
+= self
.rfile
.read(length
)
671 def EchoHandler(self
):
672 """This handler just echoes back the payload of the request, for testing
675 if not self
._ShouldHandleRequest
("/echo"):
678 _
, _
, _
, _
, query
, _
= urlparse
.urlparse(self
.path
)
679 query_params
= cgi
.parse_qs(query
, True)
680 if 'status' in query_params
:
681 self
.send_response(int(query_params
['status'][0]))
683 self
.send_response(200)
684 self
.send_header('Content-Type', 'text/html')
686 self
.wfile
.write(self
.ReadRequestBody())
689 def EchoTitleHandler(self
):
690 """This handler is like Echo, but sets the page title to the request."""
692 if not self
._ShouldHandleRequest
("/echotitle"):
695 self
.send_response(200)
696 self
.send_header('Content-Type', 'text/html')
698 request
= self
.ReadRequestBody()
699 self
.wfile
.write('<html><head><title>')
700 self
.wfile
.write(request
)
701 self
.wfile
.write('</title></head></html>')
704 def EchoAllHandler(self
):
705 """This handler yields a (more) human-readable page listing information
706 about the request header & contents."""
708 if not self
._ShouldHandleRequest
("/echoall"):
711 self
.send_response(200)
712 self
.send_header('Content-Type', 'text/html')
714 self
.wfile
.write('<html><head><style>'
715 'pre { border: 1px solid black; margin: 5px; padding: 5px }'
716 '</style></head><body>'
717 '<div style="float: right">'
718 '<a href="/echo">back to referring page</a></div>'
719 '<h1>Request Body:</h1><pre>')
721 if self
.command
== 'POST' or self
.command
== 'PUT':
722 qs
= self
.ReadRequestBody()
723 params
= cgi
.parse_qs(qs
, keep_blank_values
=1)
726 self
.wfile
.write('%s=%s\n' % (param
, params
[param
][0]))
728 self
.wfile
.write('</pre>')
730 self
.wfile
.write('<h1>Request Headers:</h1><pre>%s</pre>' % self
.headers
)
732 self
.wfile
.write('</body></html>')
735 def EchoMultipartPostHandler(self
):
736 """This handler echoes received multipart post data as json format."""
738 if not (self
._ShouldHandleRequest
("/echomultipartpost") or
739 self
._ShouldHandleRequest
("/searchbyimage")):
742 content_type
, parameters
= cgi
.parse_header(
743 self
.headers
.getheader('content-type'))
744 if content_type
== 'multipart/form-data':
745 post_multipart
= cgi
.parse_multipart(self
.rfile
, parameters
)
746 elif content_type
== 'application/x-www-form-urlencoded':
747 raise Exception('POST by application/x-www-form-urlencoded is '
752 # Since the data can be binary, we encode them by base64.
753 post_multipart_base64_encoded
= {}
754 for field
, values
in post_multipart
.items():
755 post_multipart_base64_encoded
[field
] = [base64
.b64encode(value
)
758 result
= {'POST_multipart' : post_multipart_base64_encoded
}
760 self
.send_response(200)
761 self
.send_header("Content-type", "text/plain")
763 self
.wfile
.write(json
.dumps(result
, indent
=2, sort_keys
=False))
766 def DownloadHandler(self
):
767 """This handler sends a downloadable file with or without reporting
770 if self
.path
.startswith("/download-unknown-size"):
772 elif self
.path
.startswith("/download-known-size"):
778 # The test which uses this functionality is attempting to send
779 # small chunks of data to the client. Use a fairly large buffer
780 # so that we'll fill chrome's IO buffer enough to force it to
781 # actually write the data.
782 # See also the comments in the client-side of this test in
785 size_chunk1
= 35*1024
786 size_chunk2
= 10*1024
788 self
.send_response(200)
789 self
.send_header('Content-Type', 'application/octet-stream')
790 self
.send_header('Cache-Control', 'max-age=0')
792 self
.send_header('Content-Length', size_chunk1
+ size_chunk2
)
795 # First chunk of data:
796 self
.wfile
.write("*" * size_chunk1
)
799 # handle requests until one of them clears this flag.
800 self
.server
.wait_for_download
= True
801 while self
.server
.wait_for_download
:
802 self
.server
.handle_request()
804 # Second chunk of data:
805 self
.wfile
.write("*" * size_chunk2
)
808 def DownloadFinishHandler(self
):
809 """This handler just tells the server to finish the current download."""
811 if not self
._ShouldHandleRequest
("/download-finish"):
814 self
.server
.wait_for_download
= False
815 self
.send_response(200)
816 self
.send_header('Content-Type', 'text/html')
817 self
.send_header('Cache-Control', 'max-age=0')
821 def _ReplaceFileData(self
, data
, query_parameters
):
822 """Replaces matching substrings in a file.
824 If the 'replace_text' URL query parameter is present, it is expected to be
825 of the form old_text:new_text, which indicates that any old_text strings in
826 the file are replaced with new_text. Multiple 'replace_text' parameters may
829 If the parameters are not present, |data| is returned.
832 query_dict
= cgi
.parse_qs(query_parameters
)
833 replace_text_values
= query_dict
.get('replace_text', [])
834 for replace_text_value
in replace_text_values
:
835 replace_text_args
= replace_text_value
.split(':')
836 if len(replace_text_args
) != 2:
838 'replace_text must be of form old_text:new_text. Actual value: %s' %
840 old_text_b64
, new_text_b64
= replace_text_args
841 old_text
= base64
.urlsafe_b64decode(old_text_b64
)
842 new_text
= base64
.urlsafe_b64decode(new_text_b64
)
843 data
= data
.replace(old_text
, new_text
)
846 def ZipFileHandler(self
):
847 """This handler sends the contents of the requested file in compressed form.
848 Can pass in a parameter that specifies that the content length be
849 C - the compressed size (OK),
850 U - the uncompressed size (Non-standard, but handled),
851 S - less than compressed (OK because we keep going),
852 M - larger than compressed but less than uncompressed (an error),
853 L - larger than uncompressed (an error)
854 Example: compressedfiles/Picture_1.doc?C
857 prefix
= "/compressedfiles/"
858 if not self
.path
.startswith(prefix
):
861 # Consume a request body if present.
862 if self
.command
== 'POST' or self
.command
== 'PUT' :
863 self
.ReadRequestBody()
865 _
, _
, url_path
, _
, query
, _
= urlparse
.urlparse(self
.path
)
867 if not query
in ('C', 'U', 'S', 'M', 'L'):
870 sub_path
= url_path
[len(prefix
):]
871 entries
= sub_path
.split('/')
872 file_path
= os
.path
.join(self
.server
.data_dir
, *entries
)
873 if os
.path
.isdir(file_path
):
874 file_path
= os
.path
.join(file_path
, 'index.html')
876 if not os
.path
.isfile(file_path
):
877 print "File not found " + sub_path
+ " full path:" + file_path
881 f
= open(file_path
, "rb")
883 uncompressed_len
= len(data
)
887 data
= zlib
.compress(data
)
888 compressed_len
= len(data
)
890 content_length
= compressed_len
892 content_length
= uncompressed_len
894 content_length
= compressed_len
/ 2
896 content_length
= (compressed_len
+ uncompressed_len
) / 2
898 content_length
= compressed_len
+ uncompressed_len
900 self
.send_response(200)
901 self
.send_header('Content-Type', 'application/msword')
902 self
.send_header('Content-encoding', 'deflate')
903 self
.send_header('Connection', 'close')
904 self
.send_header('Content-Length', content_length
)
905 self
.send_header('ETag', '\'' + file_path
+ '\'')
908 self
.wfile
.write(data
)
912 def FileHandler(self
):
913 """This handler sends the contents of the requested file. Wow, it's like
916 prefix
= self
.server
.file_root_url
917 if not self
.path
.startswith(prefix
):
919 return self
._FileHandlerHelper
(prefix
)
921 def PostOnlyFileHandler(self
):
922 """This handler sends the contents of the requested file on a POST."""
924 prefix
= urlparse
.urljoin(self
.server
.file_root_url
, 'post/')
925 if not self
.path
.startswith(prefix
):
927 return self
._FileHandlerHelper
(prefix
)
929 def _FileHandlerHelper(self
, prefix
):
931 if self
.command
== 'POST' or self
.command
== 'PUT':
932 # Consume a request body if present.
933 request_body
= self
.ReadRequestBody()
935 _
, _
, url_path
, _
, query
, _
= urlparse
.urlparse(self
.path
)
936 query_dict
= cgi
.parse_qs(query
)
938 expected_body
= query_dict
.get('expected_body', [])
939 if expected_body
and request_body
not in expected_body
:
940 self
.send_response(404)
945 expected_headers
= query_dict
.get('expected_headers', [])
946 for expected_header
in expected_headers
:
947 header_name
, expected_value
= expected_header
.split(':')
948 if self
.headers
.getheader(header_name
) != expected_value
:
949 self
.send_response(404)
954 sub_path
= url_path
[len(prefix
):]
955 entries
= sub_path
.split('/')
956 file_path
= os
.path
.join(self
.server
.data_dir
, *entries
)
957 if os
.path
.isdir(file_path
):
958 file_path
= os
.path
.join(file_path
, 'index.html')
960 if not os
.path
.isfile(file_path
):
961 print "File not found " + sub_path
+ " full path:" + file_path
965 f
= open(file_path
, "rb")
969 data
= self
._ReplaceFileData
(data
, query
)
971 old_protocol_version
= self
.protocol_version
973 # If file.mock-http-headers exists, it contains the headers we
974 # should send. Read them in and parse them.
975 headers_path
= file_path
+ '.mock-http-headers'
976 if os
.path
.isfile(headers_path
):
977 f
= open(headers_path
, "r")
980 response
= f
.readline()
981 http_major
, http_minor
, status_code
= re
.findall(
982 'HTTP/(\d+).(\d+) (\d+)', response
)[0]
983 self
.protocol_version
= "HTTP/%s.%s" % (http_major
, http_minor
)
984 self
.send_response(int(status_code
))
987 header_values
= re
.findall('(\S+):\s*(.*)', line
)
988 if len(header_values
) > 0:
990 name
, value
= header_values
[0]
991 self
.send_header(name
, value
)
994 # Could be more generic once we support mime-type sniffing, but for
995 # now we need to set it explicitly.
997 range_header
= self
.headers
.get('Range')
998 if range_header
and range_header
.startswith('bytes='):
999 # Note this doesn't handle all valid byte range_header values (i.e.
1000 # left open ended ones), just enough for what we needed so far.
1001 range_header
= range_header
[6:].split('-')
1002 start
= int(range_header
[0])
1004 end
= int(range_header
[1])
1008 self
.send_response(206)
1009 content_range
= ('bytes ' + str(start
) + '-' + str(end
) + '/' +
1011 self
.send_header('Content-Range', content_range
)
1012 data
= data
[start
: end
+ 1]
1014 self
.send_response(200)
1016 self
.send_header('Content-Type', self
.GetMIMETypeFromName(file_path
))
1017 self
.send_header('Accept-Ranges', 'bytes')
1018 self
.send_header('Content-Length', len(data
))
1019 self
.send_header('ETag', '\'' + file_path
+ '\'')
1022 if (self
.command
!= 'HEAD'):
1023 self
.wfile
.write(data
)
1025 self
.protocol_version
= old_protocol_version
1028 def SetCookieHandler(self
):
1029 """This handler just sets a cookie, for testing cookie handling."""
1031 if not self
._ShouldHandleRequest
("/set-cookie"):
1034 query_char
= self
.path
.find('?')
1035 if query_char
!= -1:
1036 cookie_values
= self
.path
[query_char
+ 1:].split('&')
1038 cookie_values
= ("",)
1039 self
.send_response(200)
1040 self
.send_header('Content-Type', 'text/html')
1041 for cookie_value
in cookie_values
:
1042 self
.send_header('Set-Cookie', '%s' % cookie_value
)
1044 for cookie_value
in cookie_values
:
1045 self
.wfile
.write('%s' % cookie_value
)
1048 def SetManyCookiesHandler(self
):
1049 """This handler just sets a given number of cookies, for testing handling
1050 of large numbers of cookies."""
1052 if not self
._ShouldHandleRequest
("/set-many-cookies"):
1055 query_char
= self
.path
.find('?')
1056 if query_char
!= -1:
1057 num_cookies
= int(self
.path
[query_char
+ 1:])
1060 self
.send_response(200)
1061 self
.send_header('', 'text/html')
1062 for _i
in range(0, num_cookies
):
1063 self
.send_header('Set-Cookie', 'a=')
1065 self
.wfile
.write('%d cookies were sent' % num_cookies
)
1068 def ExpectAndSetCookieHandler(self
):
1069 """Expects some cookies to be sent, and if they are, sets more cookies.
1071 The expect parameter specifies a required cookie. May be specified multiple
1073 The set parameter specifies a cookie to set if all required cookies are
1074 preset. May be specified multiple times.
1075 The data parameter specifies the response body data to be returned."""
1077 if not self
._ShouldHandleRequest
("/expect-and-set-cookie"):
1080 _
, _
, _
, _
, query
, _
= urlparse
.urlparse(self
.path
)
1081 query_dict
= cgi
.parse_qs(query
)
1083 if 'Cookie' in self
.headers
:
1084 cookie_header
= self
.headers
.getheader('Cookie')
1085 cookies
.update([s
.strip() for s
in cookie_header
.split(';')])
1086 got_all_expected_cookies
= True
1087 for expected_cookie
in query_dict
.get('expect', []):
1088 if expected_cookie
not in cookies
:
1089 got_all_expected_cookies
= False
1090 self
.send_response(200)
1091 self
.send_header('Content-Type', 'text/html')
1092 if got_all_expected_cookies
:
1093 for cookie_value
in query_dict
.get('set', []):
1094 self
.send_header('Set-Cookie', '%s' % cookie_value
)
1096 for data_value
in query_dict
.get('data', []):
1097 self
.wfile
.write(data_value
)
1100 def SetHeaderHandler(self
):
1101 """This handler sets a response header. Parameters are in the
1102 key%3A%20value&key2%3A%20value2 format."""
1104 if not self
._ShouldHandleRequest
("/set-header"):
1107 query_char
= self
.path
.find('?')
1108 if query_char
!= -1:
1109 headers_values
= self
.path
[query_char
+ 1:].split('&')
1111 headers_values
= ("",)
1112 self
.send_response(200)
1113 self
.send_header('Content-Type', 'text/html')
1114 for header_value
in headers_values
:
1115 header_value
= urllib
.unquote(header_value
)
1116 (key
, value
) = header_value
.split(': ', 1)
1117 self
.send_header(key
, value
)
1119 for header_value
in headers_values
:
1120 self
.wfile
.write('%s' % header_value
)
1123 def AuthBasicHandler(self
):
1124 """This handler tests 'Basic' authentication. It just sends a page with
1125 title 'user/pass' if you succeed."""
1127 if not self
._ShouldHandleRequest
("/auth-basic"):
1130 username
= userpass
= password
= b64str
= ""
1131 expected_password
= 'secret'
1133 set_cookie_if_challenged
= False
1135 _
, _
, url_path
, _
, query
, _
= urlparse
.urlparse(self
.path
)
1136 query_params
= cgi
.parse_qs(query
, True)
1137 if 'set-cookie-if-challenged' in query_params
:
1138 set_cookie_if_challenged
= True
1139 if 'password' in query_params
:
1140 expected_password
= query_params
['password'][0]
1141 if 'realm' in query_params
:
1142 realm
= query_params
['realm'][0]
1144 auth
= self
.headers
.getheader('authorization')
1147 raise Exception('no auth')
1148 b64str
= re
.findall(r
'Basic (\S+)', auth
)[0]
1149 userpass
= base64
.b64decode(b64str
)
1150 username
, password
= re
.findall(r
'([^:]+):(\S+)', userpass
)[0]
1151 if password
!= expected_password
:
1152 raise Exception('wrong password')
1153 except Exception, e
:
1154 # Authentication failed.
1155 self
.send_response(401)
1156 self
.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm
)
1157 self
.send_header('Content-Type', 'text/html')
1158 if set_cookie_if_challenged
:
1159 self
.send_header('Set-Cookie', 'got_challenged=true')
1161 self
.wfile
.write('<html><head>')
1162 self
.wfile
.write('<title>Denied: %s</title>' % e
)
1163 self
.wfile
.write('</head><body>')
1164 self
.wfile
.write('auth=%s<p>' % auth
)
1165 self
.wfile
.write('b64str=%s<p>' % b64str
)
1166 self
.wfile
.write('username: %s<p>' % username
)
1167 self
.wfile
.write('userpass: %s<p>' % userpass
)
1168 self
.wfile
.write('password: %s<p>' % password
)
1169 self
.wfile
.write('You sent:<br>%s<p>' % self
.headers
)
1170 self
.wfile
.write('</body></html>')
1173 # Authentication successful. (Return a cachable response to allow for
1174 # testing cached pages that require authentication.)
1175 old_protocol_version
= self
.protocol_version
1176 self
.protocol_version
= "HTTP/1.1"
1178 if_none_match
= self
.headers
.getheader('if-none-match')
1179 if if_none_match
== "abc":
1180 self
.send_response(304)
1182 elif url_path
.endswith(".gif"):
1183 # Using chrome/test/data/google/logo.gif as the test image
1184 test_image_path
= ['google', 'logo.gif']
1185 gif_path
= os
.path
.join(self
.server
.data_dir
, *test_image_path
)
1186 if not os
.path
.isfile(gif_path
):
1187 self
.send_error(404)
1188 self
.protocol_version
= old_protocol_version
1191 f
= open(gif_path
, "rb")
1195 self
.send_response(200)
1196 self
.send_header('Content-Type', 'image/gif')
1197 self
.send_header('Cache-control', 'max-age=60000')
1198 self
.send_header('Etag', 'abc')
1200 self
.wfile
.write(data
)
1202 self
.send_response(200)
1203 self
.send_header('Content-Type', 'text/html')
1204 self
.send_header('Cache-control', 'max-age=60000')
1205 self
.send_header('Etag', 'abc')
1207 self
.wfile
.write('<html><head>')
1208 self
.wfile
.write('<title>%s/%s</title>' % (username
, password
))
1209 self
.wfile
.write('</head><body>')
1210 self
.wfile
.write('auth=%s<p>' % auth
)
1211 self
.wfile
.write('You sent:<br>%s<p>' % self
.headers
)
1212 self
.wfile
.write('</body></html>')
1214 self
.protocol_version
= old_protocol_version
1217 def GetNonce(self
, force_reset
=False):
1218 """Returns a nonce that's stable per request path for the server's lifetime.
1219 This is a fake implementation. A real implementation would only use a given
1220 nonce a single time (hence the name n-once). However, for the purposes of
1221 unittesting, we don't care about the security of the nonce.
1224 force_reset: Iff set, the nonce will be changed. Useful for testing the
1228 if force_reset
or not self
.server
.nonce_time
:
1229 self
.server
.nonce_time
= time
.time()
1230 return hashlib
.md5('privatekey%s%d' %
1231 (self
.path
, self
.server
.nonce_time
)).hexdigest()
1233 def AuthDigestHandler(self
):
1234 """This handler tests 'Digest' authentication.
1236 It just sends a page with title 'user/pass' if you succeed.
1238 A stale response is sent iff "stale" is present in the request path.
1241 if not self
._ShouldHandleRequest
("/auth-digest"):
1244 stale
= 'stale' in self
.path
1245 nonce
= self
.GetNonce(force_reset
=stale
)
1246 opaque
= hashlib
.md5('opaque').hexdigest()
1250 auth
= self
.headers
.getheader('authorization')
1254 raise Exception('no auth')
1255 if not auth
.startswith('Digest'):
1256 raise Exception('not digest')
1257 # Pull out all the name="value" pairs as a dictionary.
1258 pairs
= dict(re
.findall(r
'(\b[^ ,=]+)="?([^",]+)"?', auth
))
1260 # Make sure it's all valid.
1261 if pairs
['nonce'] != nonce
:
1262 raise Exception('wrong nonce')
1263 if pairs
['opaque'] != opaque
:
1264 raise Exception('wrong opaque')
1266 # Check the 'response' value and make sure it matches our magic hash.
1267 # See http://www.ietf.org/rfc/rfc2617.txt
1268 hash_a1
= hashlib
.md5(
1269 ':'.join([pairs
['username'], realm
, password
])).hexdigest()
1270 hash_a2
= hashlib
.md5(':'.join([self
.command
, pairs
['uri']])).hexdigest()
1271 if 'qop' in pairs
and 'nc' in pairs
and 'cnonce' in pairs
:
1272 response
= hashlib
.md5(':'.join([hash_a1
, nonce
, pairs
['nc'],
1273 pairs
['cnonce'], pairs
['qop'], hash_a2
])).hexdigest()
1275 response
= hashlib
.md5(':'.join([hash_a1
, nonce
, hash_a2
])).hexdigest()
1277 if pairs
['response'] != response
:
1278 raise Exception('wrong password')
1279 except Exception, e
:
1280 # Authentication failed.
1281 self
.send_response(401)
1288 'opaque="%s"') % (realm
, nonce
, opaque
)
1290 hdr
+= ', stale="TRUE"'
1291 self
.send_header('WWW-Authenticate', hdr
)
1292 self
.send_header('Content-Type', 'text/html')
1294 self
.wfile
.write('<html><head>')
1295 self
.wfile
.write('<title>Denied: %s</title>' % e
)
1296 self
.wfile
.write('</head><body>')
1297 self
.wfile
.write('auth=%s<p>' % auth
)
1298 self
.wfile
.write('pairs=%s<p>' % pairs
)
1299 self
.wfile
.write('You sent:<br>%s<p>' % self
.headers
)
1300 self
.wfile
.write('We are replying:<br>%s<p>' % hdr
)
1301 self
.wfile
.write('</body></html>')
1304 # Authentication successful.
1305 self
.send_response(200)
1306 self
.send_header('Content-Type', 'text/html')
1308 self
.wfile
.write('<html><head>')
1309 self
.wfile
.write('<title>%s/%s</title>' % (pairs
['username'], password
))
1310 self
.wfile
.write('</head><body>')
1311 self
.wfile
.write('auth=%s<p>' % auth
)
1312 self
.wfile
.write('pairs=%s<p>' % pairs
)
1313 self
.wfile
.write('</body></html>')
1317 def SlowServerHandler(self
):
1318 """Wait for the user suggested time before responding. The syntax is
1319 /slow?0.5 to wait for half a second."""
1321 if not self
._ShouldHandleRequest
("/slow"):
1323 query_char
= self
.path
.find('?')
1327 wait_sec
= float(self
.path
[query_char
+ 1:])
1330 time
.sleep(wait_sec
)
1331 self
.send_response(200)
1332 self
.send_header('Content-Type', 'text/plain')
1334 self
.wfile
.write("waited %.1f seconds" % wait_sec
)
1337 def ChunkedServerHandler(self
):
1338 """Send chunked response. Allows to specify chunks parameters:
1339 - waitBeforeHeaders - ms to wait before sending headers
1340 - waitBetweenChunks - ms to wait between chunks
1341 - chunkSize - size of each chunk in bytes
1342 - chunksNumber - number of chunks
1343 Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5
1344 waits one second, then sends headers and five chunks five bytes each."""
1346 if not self
._ShouldHandleRequest
("/chunked"):
1348 query_char
= self
.path
.find('?')
1349 chunkedSettings
= {'waitBeforeHeaders' : 0,
1350 'waitBetweenChunks' : 0,
1354 params
= self
.path
[query_char
+ 1:].split('&')
1355 for param
in params
:
1356 keyValue
= param
.split('=')
1357 if len(keyValue
) == 2:
1359 chunkedSettings
[keyValue
[0]] = int(keyValue
[1])
1362 time
.sleep(0.001 * chunkedSettings
['waitBeforeHeaders'])
1363 self
.protocol_version
= 'HTTP/1.1' # Needed for chunked encoding
1364 self
.send_response(200)
1365 self
.send_header('Content-Type', 'text/plain')
1366 self
.send_header('Connection', 'close')
1367 self
.send_header('Transfer-Encoding', 'chunked')
1369 # Chunked encoding: sending all chunks, then final zero-length chunk and
1371 for i
in range(0, chunkedSettings
['chunksNumber']):
1373 time
.sleep(0.001 * chunkedSettings
['waitBetweenChunks'])
1374 self
.sendChunkHelp('*' * chunkedSettings
['chunkSize'])
1375 self
.wfile
.flush() # Keep in mind that we start flushing only after 1kb.
1376 self
.sendChunkHelp('')
1379 def NoContentHandler(self
):
1380 """Returns a 204 No Content response."""
1382 if not self
._ShouldHandleRequest
("/nocontent"):
1384 self
.send_response(204)
1388 def ServerRedirectHandler(self
):
1389 """Sends a server redirect to the given URL. The syntax is
1390 '/server-redirect?http://foo.bar/asdf' to redirect to
1391 'http://foo.bar/asdf'"""
1393 test_name
= "/server-redirect"
1394 if not self
._ShouldHandleRequest
(test_name
):
1397 query_char
= self
.path
.find('?')
1398 if query_char
< 0 or len(self
.path
) <= query_char
+ 1:
1399 self
.sendRedirectHelp(test_name
)
1401 dest
= urllib
.unquote(self
.path
[query_char
+ 1:])
1403 self
.send_response(301) # moved permanently
1404 self
.send_header('Location', dest
)
1405 self
.send_header('Content-Type', 'text/html')
1407 self
.wfile
.write('<html><head>')
1408 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1412 def CrossSiteRedirectHandler(self
):
1413 """Sends a server redirect to the given site. The syntax is
1414 '/cross-site/hostname/...' to redirect to //hostname/...
1415 It is used to navigate between different Sites, causing
1416 cross-site/cross-process navigations in the browser."""
1418 test_name
= "/cross-site"
1419 if not self
._ShouldHandleRequest
(test_name
):
1422 params
= urllib
.unquote(self
.path
[(len(test_name
) + 1):])
1423 slash
= params
.find('/')
1425 self
.sendRedirectHelp(test_name
)
1428 host
= params
[:slash
]
1429 path
= params
[(slash
+1):]
1430 dest
= "//%s:%s/%s" % (host
, str(self
.server
.server_port
), path
)
1432 self
.send_response(301) # moved permanently
1433 self
.send_header('Location', dest
)
1434 self
.send_header('Content-Type', 'text/html')
1436 self
.wfile
.write('<html><head>')
1437 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1441 def ClientRedirectHandler(self
):
1442 """Sends a client redirect to the given URL. The syntax is
1443 '/client-redirect?http://foo.bar/asdf' to redirect to
1444 'http://foo.bar/asdf'"""
1446 test_name
= "/client-redirect"
1447 if not self
._ShouldHandleRequest
(test_name
):
1450 query_char
= self
.path
.find('?')
1451 if query_char
< 0 or len(self
.path
) <= query_char
+ 1:
1452 self
.sendRedirectHelp(test_name
)
1454 dest
= urllib
.unquote(self
.path
[query_char
+ 1:])
1456 self
.send_response(200)
1457 self
.send_header('Content-Type', 'text/html')
1459 self
.wfile
.write('<html><head>')
1460 self
.wfile
.write('<meta http-equiv="refresh" content="0;url=%s">' % dest
)
1461 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1465 def GetSSLSessionCacheHandler(self
):
1466 """Send a reply containing a log of the session cache operations."""
1468 if not self
._ShouldHandleRequest
('/ssl-session-cache'):
1471 self
.send_response(200)
1472 self
.send_header('Content-Type', 'text/plain')
1475 log
= self
.server
.session_cache
.log
1476 except AttributeError:
1477 self
.wfile
.write('Pass --https-record-resume in order to use' +
1481 for (action
, sessionID
) in log
:
1482 self
.wfile
.write('%s\t%s\n' % (action
, bytes(sessionID
).encode('hex')))
1485 def SSLManySmallRecords(self
):
1486 """Sends a reply consisting of a variety of small writes. These will be
1487 translated into a series of small SSL records when used over an HTTPS
1490 if not self
._ShouldHandleRequest
('/ssl-many-small-records'):
1493 self
.send_response(200)
1494 self
.send_header('Content-Type', 'text/plain')
1497 # Write ~26K of data, in 1350 byte chunks
1498 for i
in xrange(20):
1499 self
.wfile
.write('*' * 1350)
1503 def GetChannelID(self
):
1504 """Send a reply containing the hashed ChannelID that the client provided."""
1506 if not self
._ShouldHandleRequest
('/channel-id'):
1509 self
.send_response(200)
1510 self
.send_header('Content-Type', 'text/plain')
1512 channel_id
= bytes(self
.server
.tlsConnection
.channel_id
)
1513 self
.wfile
.write(hashlib
.sha256(channel_id
).digest().encode('base64'))
1516 def GetClientCert(self
):
1517 """Send a reply whether a client certificate was provided."""
1519 if not self
._ShouldHandleRequest
('/client-cert'):
1522 self
.send_response(200)
1523 self
.send_header('Content-Type', 'text/plain')
1526 cert_chain
= self
.server
.tlsConnection
.session
.clientCertChain
1527 if cert_chain
!= None:
1528 self
.wfile
.write('got client cert with fingerprint: ' +
1529 cert_chain
.getFingerprint())
1531 self
.wfile
.write('got no client cert')
1534 def ClientCipherListHandler(self
):
1535 """Send a reply containing the cipher suite list that the client
1536 provided. Each cipher suite value is serialized in decimal, followed by a
1539 if not self
._ShouldHandleRequest
('/client-cipher-list'):
1542 self
.send_response(200)
1543 self
.send_header('Content-Type', 'text/plain')
1546 cipher_suites
= self
.server
.tlsConnection
.clientHello
.cipher_suites
1547 self
.wfile
.write('\n'.join(str(c
) for c
in cipher_suites
))
1550 def CloseSocketHandler(self
):
1551 """Closes the socket without sending anything."""
1553 if not self
._ShouldHandleRequest
('/close-socket'):
1559 def RangeResetHandler(self
):
1560 """Send data broken up by connection resets every N (default 4K) bytes.
1561 Support range requests. If the data requested doesn't straddle a reset
1562 boundary, it will all be sent. Used for testing resuming downloads."""
1564 def DataForRange(start
, end
):
1565 """Data to be provided for a particular range of bytes."""
1566 # Offset and scale to avoid too obvious (and hence potentially
1568 return ''.join([chr(y
% 256)
1569 for y
in range(start
* 2 + 15, end
* 2 + 15, 2)])
1571 if not self
._ShouldHandleRequest
('/rangereset'):
1574 # HTTP/1.1 is required for ETag and range support.
1575 self
.protocol_version
= 'HTTP/1.1'
1576 _
, _
, url_path
, _
, query
, _
= urlparse
.urlparse(self
.path
)
1580 # Note that the rst is sent just before sending the rst_boundary byte.
1582 respond_to_range
= True
1583 hold_for_signal
= False
1586 fail_precondition
= 0
1587 send_verifiers
= True
1590 qdict
= urlparse
.parse_qs(query
, True)
1592 size
= int(qdict
['size'][0])
1593 if 'rst_boundary' in qdict
:
1594 rst_boundary
= int(qdict
['rst_boundary'][0])
1595 if 'token' in qdict
:
1596 # Identifying token for stateful tests.
1597 token
= qdict
['token'][0]
1598 if 'rst_limit' in qdict
:
1599 # Max number of rsts for a given token.
1600 rst_limit
= int(qdict
['rst_limit'][0])
1601 if 'bounce_range' in qdict
:
1602 respond_to_range
= False
1604 # Note that hold_for_signal will not work with null range requests;
1606 hold_for_signal
= True
1607 if 'no_verifiers' in qdict
:
1608 send_verifiers
= False
1609 if 'fail_precondition' in qdict
:
1610 fail_precondition
= int(qdict
['fail_precondition'][0])
1612 # Record already set information, or set it.
1613 rst_limit
= TestPageHandler
.rst_limits
.setdefault(token
, rst_limit
)
1615 TestPageHandler
.rst_limits
[token
] -= 1
1616 fail_precondition
= TestPageHandler
.fail_precondition
.setdefault(
1617 token
, fail_precondition
)
1618 if fail_precondition
!= 0:
1619 TestPageHandler
.fail_precondition
[token
] -= 1
1622 last_byte
= size
- 1
1624 # Does that define what we want to return, or do we need to apply
1626 range_response
= False
1627 range_header
= self
.headers
.getheader('range')
1628 if range_header
and respond_to_range
:
1629 mo
= re
.match("bytes=(\d*)-(\d*)", range_header
)
1631 first_byte
= int(mo
.group(1))
1633 last_byte
= int(mo
.group(2))
1634 if last_byte
> size
- 1:
1635 last_byte
= size
- 1
1636 range_response
= True
1637 if last_byte
< first_byte
:
1640 if (fail_precondition
and
1641 (self
.headers
.getheader('If-Modified-Since') or
1642 self
.headers
.getheader('If-Match'))):
1643 self
.send_response(412)
1648 self
.send_response(206)
1649 self
.send_header('Content-Range',
1650 'bytes %d-%d/%d' % (first_byte
, last_byte
, size
))
1652 self
.send_response(200)
1653 self
.send_header('Content-Type', 'application/octet-stream')
1654 self
.send_header('Content-Length', last_byte
- first_byte
+ 1)
1656 # If fail_precondition is non-zero, then the ETag for each request will be
1658 etag
= "%s%d" % (token
, fail_precondition
)
1659 self
.send_header('ETag', etag
)
1660 self
.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
1664 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1665 # a single byte, the self.server.handle_request() below hangs
1666 # without processing new incoming requests.
1667 self
.wfile
.write(DataForRange(first_byte
, first_byte
+ 1))
1668 first_byte
= first_byte
+ 1
1669 # handle requests until one of them clears this flag.
1670 self
.server
.wait_for_download
= True
1671 while self
.server
.wait_for_download
:
1672 self
.server
.handle_request()
1674 possible_rst
= ((first_byte
/ rst_boundary
) + 1) * rst_boundary
1675 if possible_rst
>= last_byte
or rst_limit
== 0:
1676 # No RST has been requested in this range, so we don't need to
1677 # do anything fancy; just write the data and let the python
1678 # infrastructure close the connection.
1679 self
.wfile
.write(DataForRange(first_byte
, last_byte
+ 1))
1683 # We're resetting the connection part way in; go to the RST
1684 # boundary and then send an RST.
1685 # Because socket semantics do not guarantee that all the data will be
1686 # sent when using the linger semantics to hard close a socket,
1687 # we send the data and then wait for our peer to release us
1688 # before sending the reset.
1689 data
= DataForRange(first_byte
, possible_rst
)
1690 self
.wfile
.write(data
)
1692 self
.server
.wait_for_download
= True
1693 while self
.server
.wait_for_download
:
1694 self
.server
.handle_request()
1695 l_onoff
= 1 # Linger is active.
1696 l_linger
= 0 # Seconds to linger for.
1697 self
.connection
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_LINGER
,
1698 struct
.pack('ii', l_onoff
, l_linger
))
1700 # Close all duplicates of the underlying socket to force the RST.
1703 self
.connection
.close()
1707 def DefaultResponseHandler(self
):
1708 """This is the catch-all response handler for requests that aren't handled
1709 by one of the special handlers above.
1710 Note that we specify the content-length as without it the https connection
1711 is not closed properly (and the browser keeps expecting data)."""
1713 contents
= "Default response given for path: " + self
.path
1714 self
.send_response(200)
1715 self
.send_header('Content-Type', 'text/html')
1716 self
.send_header('Content-Length', len(contents
))
1718 if (self
.command
!= 'HEAD'):
1719 self
.wfile
.write(contents
)
1722 def RedirectConnectHandler(self
):
1723 """Sends a redirect to the CONNECT request for www.redirect.com. This
1724 response is not specified by the RFC, so the browser should not follow
1727 if (self
.path
.find("www.redirect.com") < 0):
1730 dest
= "http://www.destination.com/foo.js"
1732 self
.send_response(302) # moved temporarily
1733 self
.send_header('Location', dest
)
1734 self
.send_header('Connection', 'close')
1738 def ServerAuthConnectHandler(self
):
1739 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1740 response doesn't make sense because the proxy server cannot request
1741 server authentication."""
1743 if (self
.path
.find("www.server-auth.com") < 0):
1746 challenge
= 'Basic realm="WallyWorld"'
1748 self
.send_response(401) # unauthorized
1749 self
.send_header('WWW-Authenticate', challenge
)
1750 self
.send_header('Connection', 'close')
1754 def DefaultConnectResponseHandler(self
):
1755 """This is the catch-all response handler for CONNECT requests that aren't
1756 handled by one of the special handlers above. Real Web servers respond
1757 with 400 to CONNECT requests."""
1759 contents
= "Your client has issued a malformed or illegal request."
1760 self
.send_response(400) # bad request
1761 self
.send_header('Content-Type', 'text/html')
1762 self
.send_header('Content-Length', len(contents
))
1764 self
.wfile
.write(contents
)
1767 # called by the redirect handling function when there is no parameter
1768 def sendRedirectHelp(self
, redirect_name
):
1769 self
.send_response(200)
1770 self
.send_header('Content-Type', 'text/html')
1772 self
.wfile
.write('<html><body><h1>Error: no redirect destination</h1>')
1773 self
.wfile
.write('Use <pre>%s?http://dest...</pre>' % redirect_name
)
1774 self
.wfile
.write('</body></html>')
1776 # called by chunked handling function
1777 def sendChunkHelp(self
, chunk
):
1778 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1779 self
.wfile
.write('%X\r\n' % len(chunk
))
1780 self
.wfile
.write(chunk
)
1781 self
.wfile
.write('\r\n')
1784 class OCSPHandler(testserver_base
.BasePageHandler
):
1785 def __init__(self
, request
, client_address
, socket_server
):
1786 handlers
= [self
.OCSPResponse
]
1787 self
.ocsp_response
= socket_server
.ocsp_response
1788 testserver_base
.BasePageHandler
.__init
__(self
, request
, client_address
,
1789 socket_server
, [], handlers
, [],
1792 def OCSPResponse(self
):
1793 self
.send_response(200)
1794 self
.send_header('Content-Type', 'application/ocsp-response')
1795 self
.send_header('Content-Length', str(len(self
.ocsp_response
)))
1798 self
.wfile
.write(self
.ocsp_response
)
1801 class TCPEchoHandler(SocketServer
.BaseRequestHandler
):
1802 """The RequestHandler class for TCP echo server.
1804 It is instantiated once per connection to the server, and overrides the
1805 handle() method to implement communication to the client.
1809 """Handles the request from the client and constructs a response."""
1811 data
= self
.request
.recv(65536).strip()
1812 # Verify the "echo request" message received from the client. Send back
1813 # "echo response" message if "echo request" message is valid.
1815 return_data
= echo_message
.GetEchoResponseData(data
)
1821 self
.request
.send(return_data
)
1824 class UDPEchoHandler(SocketServer
.BaseRequestHandler
):
1825 """The RequestHandler class for UDP echo server.
1827 It is instantiated once per connection to the server, and overrides the
1828 handle() method to implement communication to the client.
1832 """Handles the request from the client and constructs a response."""
1834 data
= self
.request
[0].strip()
1835 request_socket
= self
.request
[1]
1836 # Verify the "echo request" message received from the client. Send back
1837 # "echo response" message if "echo request" message is valid.
1839 return_data
= echo_message
.GetEchoResponseData(data
)
1844 request_socket
.sendto(return_data
, self
.client_address
)
1847 class BasicAuthProxyRequestHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
1848 """A request handler that behaves as a proxy server which requires
1849 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1852 _AUTH_CREDENTIAL
= 'Basic Zm9vOmJhcg==' # foo:bar
1854 def parse_request(self
):
1855 """Overrides parse_request to check credential."""
1857 if not BaseHTTPServer
.BaseHTTPRequestHandler
.parse_request(self
):
1860 auth
= self
.headers
.getheader('Proxy-Authorization')
1861 if auth
!= self
._AUTH
_CREDENTIAL
:
1862 self
.send_response(407)
1863 self
.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1869 def _start_read_write(self
, sock
):
1871 self
.request
.setblocking(0)
1872 rlist
= [self
.request
, sock
]
1874 ready_sockets
, _unused
, errors
= select
.select(rlist
, [], [])
1876 self
.send_response(500)
1879 for s
in ready_sockets
:
1880 received
= s
.recv(1024)
1881 if len(received
) == 0:
1883 if s
== self
.request
:
1886 other
= self
.request
1887 other
.send(received
)
1889 def _do_common_method(self
):
1890 url
= urlparse
.urlparse(self
.path
)
1893 if url
.scheme
== 'http':
1895 elif url
.scheme
== 'https':
1897 if not url
.hostname
or not port
:
1898 self
.send_response(400)
1902 if len(url
.path
) == 0:
1906 if len(url
.query
) > 0:
1907 path
= '%s?%s' % (url
.path
, url
.query
)
1911 sock
= socket
.create_connection((url
.hostname
, port
))
1912 sock
.send('%s %s %s\r\n' % (
1913 self
.command
, path
, self
.protocol_version
))
1914 for header
in self
.headers
.headers
:
1915 header
= header
.strip()
1916 if (header
.lower().startswith('connection') or
1917 header
.lower().startswith('proxy')):
1919 sock
.send('%s\r\n' % header
)
1921 self
._start
_read
_write
(sock
)
1923 self
.send_response(500)
1926 if sock
is not None:
1929 def do_CONNECT(self
):
1931 pos
= self
.path
.rfind(':')
1932 host
= self
.path
[:pos
]
1933 port
= int(self
.path
[pos
+1:])
1935 self
.send_response(400)
1939 sock
= socket
.create_connection((host
, port
))
1940 self
.send_response(200, 'Connection established')
1942 self
._start
_read
_write
(sock
)
1944 self
.send_response(500)
1950 self
._do
_common
_method
()
1953 self
._do
_common
_method
()
1956 class ServerRunner(testserver_base
.TestServerRunner
):
1957 """TestServerRunner for the net test servers."""
1960 super(ServerRunner
, self
).__init
__()
1961 self
.__ocsp
_server
= None
1963 def __make_data_dir(self
):
1964 if self
.options
.data_dir
:
1965 if not os
.path
.isdir(self
.options
.data_dir
):
1966 raise testserver_base
.OptionError('specified data dir not found: ' +
1967 self
.options
.data_dir
+ ' exiting...')
1968 my_data_dir
= self
.options
.data_dir
1970 # Create the default path to our data dir, relative to the exe dir.
1971 my_data_dir
= os
.path
.join(BASE_DIR
, "..", "..", "..", "..",
1974 #TODO(ibrar): Must use Find* funtion defined in google\tools
1975 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1979 def create_server(self
, server_data
):
1980 port
= self
.options
.port
1981 host
= self
.options
.host
1983 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1984 # will result in a call to |getaddrinfo|, which fails with "nodename
1985 # nor servname provided" for localhost:0 on 10.6.
1986 if self
.options
.server_type
== SERVER_WEBSOCKET
and \
1987 host
== "localhost" and \
1991 if self
.options
.server_type
== SERVER_HTTP
:
1992 if self
.options
.https
:
1993 pem_cert_and_key
= None
1995 if self
.options
.cert_and_key_file
:
1996 if not os
.path
.isfile(self
.options
.cert_and_key_file
):
1997 raise testserver_base
.OptionError(
1998 'specified server cert file not found: ' +
1999 self
.options
.cert_and_key_file
+ ' exiting...')
2000 pem_cert_and_key
= file(self
.options
.cert_and_key_file
, 'r').read()
2002 # generate a new certificate and run an OCSP server for it.
2003 self
.__ocsp
_server
= OCSPServer((host
, 0), OCSPHandler
)
2004 print ('OCSP server started on %s:%d...' %
2005 (host
, self
.__ocsp
_server
.server_port
))
2009 if self
.options
.ocsp
== 'ok':
2010 ocsp_state
= minica
.OCSP_STATE_GOOD
2011 elif self
.options
.ocsp
== 'revoked':
2012 ocsp_state
= minica
.OCSP_STATE_REVOKED
2013 elif self
.options
.ocsp
== 'invalid':
2014 ocsp_state
= minica
.OCSP_STATE_INVALID
2015 elif self
.options
.ocsp
== 'unauthorized':
2016 ocsp_state
= minica
.OCSP_STATE_UNAUTHORIZED
2017 elif self
.options
.ocsp
== 'unknown':
2018 ocsp_state
= minica
.OCSP_STATE_UNKNOWN
2020 raise testserver_base
.OptionError('unknown OCSP status: ' +
2021 self
.options
.ocsp_status
)
2023 (pem_cert_and_key
, ocsp_der
) = minica
.GenerateCertKeyAndOCSP(
2024 subject
= "127.0.0.1",
2025 ocsp_url
= ("http://%s:%d/ocsp" %
2026 (host
, self
.__ocsp
_server
.server_port
)),
2027 ocsp_state
= ocsp_state
,
2028 serial
= self
.options
.cert_serial
)
2030 if self
.options
.ocsp_server_unavailable
:
2031 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2032 self
.__ocsp
_server
.ocsp_response
= '30030a0103'.decode('hex')
2034 self
.__ocsp
_server
.ocsp_response
= ocsp_der
2036 for ca_cert
in self
.options
.ssl_client_ca
:
2037 if not os
.path
.isfile(ca_cert
):
2038 raise testserver_base
.OptionError(
2039 'specified trusted client CA file not found: ' + ca_cert
+
2042 stapled_ocsp_response
= None
2043 if self
.options
.staple_ocsp_response
:
2044 stapled_ocsp_response
= ocsp_der
2046 server
= HTTPSServer((host
, port
), TestPageHandler
, pem_cert_and_key
,
2047 self
.options
.ssl_client_auth
,
2048 self
.options
.ssl_client_ca
,
2049 self
.options
.ssl_client_cert_type
,
2050 self
.options
.ssl_bulk_cipher
,
2051 self
.options
.ssl_key_exchange
,
2052 self
.options
.enable_npn
,
2053 self
.options
.record_resume
,
2054 self
.options
.tls_intolerant
,
2055 self
.options
.tls_intolerance_type
,
2056 self
.options
.signed_cert_timestamps_tls_ext
.decode(
2058 self
.options
.fallback_scsv
,
2059 stapled_ocsp_response
,
2060 self
.options
.alert_after_handshake
)
2061 print 'HTTPS server started on https://%s:%d...' % \
2062 (host
, server
.server_port
)
2064 server
= HTTPServer((host
, port
), TestPageHandler
)
2065 print 'HTTP server started on http://%s:%d...' % \
2066 (host
, server
.server_port
)
2068 server
.data_dir
= self
.__make
_data
_dir
()
2069 server
.file_root_url
= self
.options
.file_root_url
2070 server_data
['port'] = server
.server_port
2071 elif self
.options
.server_type
== SERVER_WEBSOCKET
:
2072 # Launch pywebsocket via WebSocketServer.
2073 logger
= logging
.getLogger()
2074 logger
.addHandler(logging
.StreamHandler())
2075 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2076 # is required to work correctly. It should be fixed from pywebsocket side.
2077 os
.chdir(self
.__make
_data
_dir
())
2078 websocket_options
= WebSocketOptions(host
, port
, '.')
2080 if self
.options
.cert_and_key_file
:
2082 websocket_options
.use_tls
= True
2083 websocket_options
.private_key
= self
.options
.cert_and_key_file
2084 websocket_options
.certificate
= self
.options
.cert_and_key_file
2085 if self
.options
.ssl_client_auth
:
2086 websocket_options
.tls_client_cert_optional
= False
2087 websocket_options
.tls_client_auth
= True
2088 if len(self
.options
.ssl_client_ca
) != 1:
2089 raise testserver_base
.OptionError(
2090 'one trusted client CA file should be specified')
2091 if not os
.path
.isfile(self
.options
.ssl_client_ca
[0]):
2092 raise testserver_base
.OptionError(
2093 'specified trusted client CA file not found: ' +
2094 self
.options
.ssl_client_ca
[0] + ' exiting...')
2095 websocket_options
.tls_client_ca
= self
.options
.ssl_client_ca
[0]
2096 print 'Trying to start websocket server on %s://%s:%d...' % \
2097 (scheme
, websocket_options
.server_host
, websocket_options
.port
)
2098 server
= WebSocketServer(websocket_options
)
2099 print 'WebSocket server started on %s://%s:%d...' % \
2100 (scheme
, host
, server
.server_port
)
2101 server_data
['port'] = server
.server_port
2102 websocket_options
.use_basic_auth
= self
.options
.ws_basic_auth
2103 elif self
.options
.server_type
== SERVER_TCP_ECHO
:
2104 # Used for generating the key (randomly) that encodes the "echo request"
2107 server
= TCPEchoServer((host
, port
), TCPEchoHandler
)
2108 print 'Echo TCP server started on port %d...' % server
.server_port
2109 server_data
['port'] = server
.server_port
2110 elif self
.options
.server_type
== SERVER_UDP_ECHO
:
2111 # Used for generating the key (randomly) that encodes the "echo request"
2114 server
= UDPEchoServer((host
, port
), UDPEchoHandler
)
2115 print 'Echo UDP server started on port %d...' % server
.server_port
2116 server_data
['port'] = server
.server_port
2117 elif self
.options
.server_type
== SERVER_BASIC_AUTH_PROXY
:
2118 server
= HTTPServer((host
, port
), BasicAuthProxyRequestHandler
)
2119 print 'BasicAuthProxy server started on port %d...' % server
.server_port
2120 server_data
['port'] = server
.server_port
2121 elif self
.options
.server_type
== SERVER_FTP
:
2122 my_data_dir
= self
.__make
_data
_dir
()
2124 # Instantiate a dummy authorizer for managing 'virtual' users
2125 authorizer
= pyftpdlib
.ftpserver
.DummyAuthorizer()
2127 # Define a new user having full r/w permissions
2128 authorizer
.add_user('chrome', 'chrome', my_data_dir
, perm
='elradfmw')
2130 # Define a read-only anonymous user unless disabled
2131 if not self
.options
.no_anonymous_ftp_user
:
2132 authorizer
.add_anonymous(my_data_dir
)
2134 # Instantiate FTP handler class
2135 ftp_handler
= pyftpdlib
.ftpserver
.FTPHandler
2136 ftp_handler
.authorizer
= authorizer
2138 # Define a customized banner (string returned when client connects)
2139 ftp_handler
.banner
= ("pyftpdlib %s based ftpd ready." %
2140 pyftpdlib
.ftpserver
.__ver
__)
2142 # Instantiate FTP server class and listen to address:port
2143 server
= pyftpdlib
.ftpserver
.FTPServer((host
, port
), ftp_handler
)
2144 server_data
['port'] = server
.socket
.getsockname()[1]
2145 print 'FTP server started on port %d...' % server_data
['port']
2147 raise testserver_base
.OptionError('unknown server type' +
2148 self
.options
.server_type
)
2152 def run_server(self
):
2153 if self
.__ocsp
_server
:
2154 self
.__ocsp
_server
.serve_forever_on_thread()
2156 testserver_base
.TestServerRunner
.run_server(self
)
2158 if self
.__ocsp
_server
:
2159 self
.__ocsp
_server
.stop_serving()
2161 def add_options(self
):
2162 testserver_base
.TestServerRunner
.add_options(self
)
2163 self
.option_parser
.add_option('-f', '--ftp', action
='store_const',
2164 const
=SERVER_FTP
, default
=SERVER_HTTP
,
2166 help='start up an FTP server.')
2167 self
.option_parser
.add_option('--tcp-echo', action
='store_const',
2168 const
=SERVER_TCP_ECHO
, default
=SERVER_HTTP
,
2170 help='start up a tcp echo server.')
2171 self
.option_parser
.add_option('--udp-echo', action
='store_const',
2172 const
=SERVER_UDP_ECHO
, default
=SERVER_HTTP
,
2174 help='start up a udp echo server.')
2175 self
.option_parser
.add_option('--basic-auth-proxy', action
='store_const',
2176 const
=SERVER_BASIC_AUTH_PROXY
,
2177 default
=SERVER_HTTP
, dest
='server_type',
2178 help='start up a proxy server which requires '
2179 'basic authentication.')
2180 self
.option_parser
.add_option('--websocket', action
='store_const',
2181 const
=SERVER_WEBSOCKET
, default
=SERVER_HTTP
,
2183 help='start up a WebSocket server.')
2184 self
.option_parser
.add_option('--https', action
='store_true',
2185 dest
='https', help='Specify that https '
2187 self
.option_parser
.add_option('--cert-and-key-file',
2188 dest
='cert_and_key_file', help='specify the '
2189 'path to the file containing the certificate '
2190 'and private key for the server in PEM '
2192 self
.option_parser
.add_option('--ocsp', dest
='ocsp', default
='ok',
2193 help='The type of OCSP response generated '
2194 'for the automatically generated '
2195 'certificate. One of [ok,revoked,invalid]')
2196 self
.option_parser
.add_option('--cert-serial', dest
='cert_serial',
2197 default
=0, type=int,
2198 help='If non-zero then the generated '
2199 'certificate will have this serial number')
2200 self
.option_parser
.add_option('--tls-intolerant', dest
='tls_intolerant',
2201 default
='0', type='int',
2202 help='If nonzero, certain TLS connections '
2203 'will be aborted in order to test version '
2204 'fallback. 1 means all TLS versions will be '
2205 'aborted. 2 means TLS 1.1 or higher will be '
2206 'aborted. 3 means TLS 1.2 or higher will be '
2208 self
.option_parser
.add_option('--tls-intolerance-type',
2209 dest
='tls_intolerance_type',
2211 help='Controls how the server reacts to a '
2212 'TLS version it is intolerant to. Valid '
2213 'values are "alert", "close", and "reset".')
2214 self
.option_parser
.add_option('--signed-cert-timestamps-tls-ext',
2215 dest
='signed_cert_timestamps_tls_ext',
2217 help='Base64 encoded SCT list. If set, '
2218 'server will respond with a '
2219 'signed_certificate_timestamp TLS extension '
2220 'whenever the client supports it.')
2221 self
.option_parser
.add_option('--fallback-scsv', dest
='fallback_scsv',
2222 default
=False, const
=True,
2223 action
='store_const',
2224 help='If given, TLS_FALLBACK_SCSV support '
2225 'will be enabled. This causes the server to '
2226 'reject fallback connections from compatible '
2227 'clients (e.g. Chrome).')
2228 self
.option_parser
.add_option('--staple-ocsp-response',
2229 dest
='staple_ocsp_response',
2230 default
=False, action
='store_true',
2231 help='If set, server will staple the OCSP '
2232 'response whenever OCSP is on and the client '
2233 'supports OCSP stapling.')
2234 self
.option_parser
.add_option('--https-record-resume',
2235 dest
='record_resume', const
=True,
2236 default
=False, action
='store_const',
2237 help='Record resumption cache events rather '
2238 'than resuming as normal. Allows the use of '
2239 'the /ssl-session-cache request')
2240 self
.option_parser
.add_option('--ssl-client-auth', action
='store_true',
2241 help='Require SSL client auth on every '
2243 self
.option_parser
.add_option('--ssl-client-ca', action
='append',
2244 default
=[], help='Specify that the client '
2245 'certificate request should include the CA '
2246 'named in the subject of the DER-encoded '
2247 'certificate contained in the specified '
2248 'file. This option may appear multiple '
2249 'times, indicating multiple CA names should '
2250 'be sent in the request.')
2251 self
.option_parser
.add_option('--ssl-client-cert-type', action
='append',
2252 default
=[], help='Specify that the client '
2253 'certificate request should include the '
2254 'specified certificate_type value. This '
2255 'option may appear multiple times, '
2256 'indicating multiple values should be send '
2257 'in the request. Valid values are '
2258 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2259 'If omitted, "rsa_sign" will be used.')
2260 self
.option_parser
.add_option('--ssl-bulk-cipher', action
='append',
2261 help='Specify the bulk encryption '
2262 'algorithm(s) that will be accepted by the '
2263 'SSL server. Valid values are "aes128gcm", '
2264 '"aes256", "aes128", "3des", "rc4". If '
2265 'omitted, all algorithms will be used. This '
2266 'option may appear multiple times, '
2267 'indicating multiple algorithms should be '
2269 self
.option_parser
.add_option('--ssl-key-exchange', action
='append',
2270 help='Specify the key exchange algorithm(s)'
2271 'that will be accepted by the SSL server. '
2272 'Valid values are "rsa", "dhe_rsa", '
2273 '"ecdhe_rsa". If omitted, all algorithms '
2274 'will be used. This option may appear '
2275 'multiple times, indicating multiple '
2276 'algorithms should be enabled.');
2277 # TODO(davidben): Add ALPN support to tlslite.
2278 self
.option_parser
.add_option('--enable-npn', dest
='enable_npn',
2279 default
=False, const
=True,
2280 action
='store_const',
2281 help='Enable server support for the NPN '
2282 'extension. The server will advertise '
2283 'support for exactly one protocol, http/1.1')
2284 self
.option_parser
.add_option('--file-root-url', default
='/files/',
2285 help='Specify a root URL for files served.')
2286 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2287 self
.option_parser
.add_option('--ws-basic-auth', action
='store_true',
2288 dest
='ws_basic_auth',
2289 help='Enable basic-auth for WebSocket')
2290 self
.option_parser
.add_option('--ocsp-server-unavailable',
2291 dest
='ocsp_server_unavailable',
2292 default
=False, action
='store_true',
2293 help='If set, the OCSP server will return '
2294 'a tryLater status rather than the actual '
2296 self
.option_parser
.add_option('--alert-after-handshake',
2297 dest
='alert_after_handshake',
2298 default
=False, action
='store_true',
2299 help='If set, the server will send a fatal '
2300 'alert immediately after the handshake.')
2301 self
.option_parser
.add_option('--no-anonymous-ftp-user',
2302 dest
='no_anonymous_ftp_user',
2303 default
=False, action
='store_true',
2304 help='If set, the FTP server will not create '
2305 'an anonymous user.')
2308 if __name__
== '__main__':
2309 sys
.exit(ServerRunner().main())