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
.ContentTypeHandler
,
333 self
.NoContentHandler
,
334 self
.ServerRedirectHandler
,
335 self
.CrossSiteRedirectHandler
,
336 self
.ClientRedirectHandler
,
337 self
.GetSSLSessionCacheHandler
,
338 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 5 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 5 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 ContentTypeHandler(self
):
1380 """Returns a string of html with the given content type. E.g.,
1381 /contenttype?text/css returns an html file with the Content-Type
1382 header set to text/css."""
1384 if not self
._ShouldHandleRequest
("/contenttype"):
1386 query_char
= self
.path
.find('?')
1387 content_type
= self
.path
[query_char
+ 1:].strip()
1388 if not content_type
:
1389 content_type
= 'text/html'
1390 self
.send_response(200)
1391 self
.send_header('Content-Type', content_type
)
1393 self
.wfile
.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n")
1396 def NoContentHandler(self
):
1397 """Returns a 204 No Content response."""
1399 if not self
._ShouldHandleRequest
("/nocontent"):
1401 self
.send_response(204)
1405 def ServerRedirectHandler(self
):
1406 """Sends a server redirect to the given URL. The syntax is
1407 '/server-redirect?http://foo.bar/asdf' to redirect to
1408 'http://foo.bar/asdf'"""
1410 test_name
= "/server-redirect"
1411 if not self
._ShouldHandleRequest
(test_name
):
1414 query_char
= self
.path
.find('?')
1415 if query_char
< 0 or len(self
.path
) <= query_char
+ 1:
1416 self
.sendRedirectHelp(test_name
)
1418 dest
= urllib
.unquote(self
.path
[query_char
+ 1:])
1420 self
.send_response(301) # moved permanently
1421 self
.send_header('Location', dest
)
1422 self
.send_header('Content-Type', 'text/html')
1424 self
.wfile
.write('<html><head>')
1425 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1429 def CrossSiteRedirectHandler(self
):
1430 """Sends a server redirect to the given site. The syntax is
1431 '/cross-site/hostname/...' to redirect to //hostname/...
1432 It is used to navigate between different Sites, causing
1433 cross-site/cross-process navigations in the browser."""
1435 test_name
= "/cross-site"
1436 if not self
._ShouldHandleRequest
(test_name
):
1439 params
= urllib
.unquote(self
.path
[(len(test_name
) + 1):])
1440 slash
= params
.find('/')
1442 self
.sendRedirectHelp(test_name
)
1445 host
= params
[:slash
]
1446 path
= params
[(slash
+1):]
1447 dest
= "//%s:%s/%s" % (host
, str(self
.server
.server_port
), path
)
1449 self
.send_response(301) # moved permanently
1450 self
.send_header('Location', dest
)
1451 self
.send_header('Content-Type', 'text/html')
1453 self
.wfile
.write('<html><head>')
1454 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1458 def ClientRedirectHandler(self
):
1459 """Sends a client redirect to the given URL. The syntax is
1460 '/client-redirect?http://foo.bar/asdf' to redirect to
1461 'http://foo.bar/asdf'"""
1463 test_name
= "/client-redirect"
1464 if not self
._ShouldHandleRequest
(test_name
):
1467 query_char
= self
.path
.find('?')
1468 if query_char
< 0 or len(self
.path
) <= query_char
+ 1:
1469 self
.sendRedirectHelp(test_name
)
1471 dest
= urllib
.unquote(self
.path
[query_char
+ 1:])
1473 self
.send_response(200)
1474 self
.send_header('Content-Type', 'text/html')
1476 self
.wfile
.write('<html><head>')
1477 self
.wfile
.write('<meta http-equiv="refresh" content="0;url=%s">' % dest
)
1478 self
.wfile
.write('</head><body>Redirecting to %s</body></html>' % dest
)
1482 def GetSSLSessionCacheHandler(self
):
1483 """Send a reply containing a log of the session cache operations."""
1485 if not self
._ShouldHandleRequest
('/ssl-session-cache'):
1488 self
.send_response(200)
1489 self
.send_header('Content-Type', 'text/plain')
1492 log
= self
.server
.session_cache
.log
1493 except AttributeError:
1494 self
.wfile
.write('Pass --https-record-resume in order to use' +
1498 for (action
, sessionID
) in log
:
1499 self
.wfile
.write('%s\t%s\n' % (action
, bytes(sessionID
).encode('hex')))
1502 def SSLManySmallRecords(self
):
1503 """Sends a reply consisting of a variety of small writes. These will be
1504 translated into a series of small SSL records when used over an HTTPS
1507 if not self
._ShouldHandleRequest
('/ssl-many-small-records'):
1510 self
.send_response(200)
1511 self
.send_header('Content-Type', 'text/plain')
1514 # Write ~26K of data, in 1350 byte chunks
1515 for i
in xrange(20):
1516 self
.wfile
.write('*' * 1350)
1520 def GetChannelID(self
):
1521 """Send a reply containing the hashed ChannelID that the client provided."""
1523 if not self
._ShouldHandleRequest
('/channel-id'):
1526 self
.send_response(200)
1527 self
.send_header('Content-Type', 'text/plain')
1529 channel_id
= bytes(self
.server
.tlsConnection
.channel_id
)
1530 self
.wfile
.write(hashlib
.sha256(channel_id
).digest().encode('base64'))
1533 def ClientCipherListHandler(self
):
1534 """Send a reply containing the cipher suite list that the client
1535 provided. Each cipher suite value is serialized in decimal, followed by a
1538 if not self
._ShouldHandleRequest
('/client-cipher-list'):
1541 self
.send_response(200)
1542 self
.send_header('Content-Type', 'text/plain')
1545 cipher_suites
= self
.server
.tlsConnection
.clientHello
.cipher_suites
1546 self
.wfile
.write('\n'.join(str(c
) for c
in cipher_suites
))
1549 def CloseSocketHandler(self
):
1550 """Closes the socket without sending anything."""
1552 if not self
._ShouldHandleRequest
('/close-socket'):
1558 def RangeResetHandler(self
):
1559 """Send data broken up by connection resets every N (default 4K) bytes.
1560 Support range requests. If the data requested doesn't straddle a reset
1561 boundary, it will all be sent. Used for testing resuming downloads."""
1563 def DataForRange(start
, end
):
1564 """Data to be provided for a particular range of bytes."""
1565 # Offset and scale to avoid too obvious (and hence potentially
1567 return ''.join([chr(y
% 256)
1568 for y
in range(start
* 2 + 15, end
* 2 + 15, 2)])
1570 if not self
._ShouldHandleRequest
('/rangereset'):
1573 # HTTP/1.1 is required for ETag and range support.
1574 self
.protocol_version
= 'HTTP/1.1'
1575 _
, _
, url_path
, _
, query
, _
= urlparse
.urlparse(self
.path
)
1579 # Note that the rst is sent just before sending the rst_boundary byte.
1581 respond_to_range
= True
1582 hold_for_signal
= False
1585 fail_precondition
= 0
1586 send_verifiers
= True
1589 qdict
= urlparse
.parse_qs(query
, True)
1591 size
= int(qdict
['size'][0])
1592 if 'rst_boundary' in qdict
:
1593 rst_boundary
= int(qdict
['rst_boundary'][0])
1594 if 'token' in qdict
:
1595 # Identifying token for stateful tests.
1596 token
= qdict
['token'][0]
1597 if 'rst_limit' in qdict
:
1598 # Max number of rsts for a given token.
1599 rst_limit
= int(qdict
['rst_limit'][0])
1600 if 'bounce_range' in qdict
:
1601 respond_to_range
= False
1603 # Note that hold_for_signal will not work with null range requests;
1605 hold_for_signal
= True
1606 if 'no_verifiers' in qdict
:
1607 send_verifiers
= False
1608 if 'fail_precondition' in qdict
:
1609 fail_precondition
= int(qdict
['fail_precondition'][0])
1611 # Record already set information, or set it.
1612 rst_limit
= TestPageHandler
.rst_limits
.setdefault(token
, rst_limit
)
1614 TestPageHandler
.rst_limits
[token
] -= 1
1615 fail_precondition
= TestPageHandler
.fail_precondition
.setdefault(
1616 token
, fail_precondition
)
1617 if fail_precondition
!= 0:
1618 TestPageHandler
.fail_precondition
[token
] -= 1
1621 last_byte
= size
- 1
1623 # Does that define what we want to return, or do we need to apply
1625 range_response
= False
1626 range_header
= self
.headers
.getheader('range')
1627 if range_header
and respond_to_range
:
1628 mo
= re
.match("bytes=(\d*)-(\d*)", range_header
)
1630 first_byte
= int(mo
.group(1))
1632 last_byte
= int(mo
.group(2))
1633 if last_byte
> size
- 1:
1634 last_byte
= size
- 1
1635 range_response
= True
1636 if last_byte
< first_byte
:
1639 if (fail_precondition
and
1640 (self
.headers
.getheader('If-Modified-Since') or
1641 self
.headers
.getheader('If-Match'))):
1642 self
.send_response(412)
1647 self
.send_response(206)
1648 self
.send_header('Content-Range',
1649 'bytes %d-%d/%d' % (first_byte
, last_byte
, size
))
1651 self
.send_response(200)
1652 self
.send_header('Content-Type', 'application/octet-stream')
1653 self
.send_header('Content-Length', last_byte
- first_byte
+ 1)
1655 # If fail_precondition is non-zero, then the ETag for each request will be
1657 etag
= "%s%d" % (token
, fail_precondition
)
1658 self
.send_header('ETag', etag
)
1659 self
.send_header('Last-Modified', 'Tue, 19 Feb 2013 14:32 EST')
1663 # TODO(rdsmith/phajdan.jr): http://crbug.com/169519: Without writing
1664 # a single byte, the self.server.handle_request() below hangs
1665 # without processing new incoming requests.
1666 self
.wfile
.write(DataForRange(first_byte
, first_byte
+ 1))
1667 first_byte
= first_byte
+ 1
1668 # handle requests until one of them clears this flag.
1669 self
.server
.wait_for_download
= True
1670 while self
.server
.wait_for_download
:
1671 self
.server
.handle_request()
1673 possible_rst
= ((first_byte
/ rst_boundary
) + 1) * rst_boundary
1674 if possible_rst
>= last_byte
or rst_limit
== 0:
1675 # No RST has been requested in this range, so we don't need to
1676 # do anything fancy; just write the data and let the python
1677 # infrastructure close the connection.
1678 self
.wfile
.write(DataForRange(first_byte
, last_byte
+ 1))
1682 # We're resetting the connection part way in; go to the RST
1683 # boundary and then send an RST.
1684 # Because socket semantics do not guarantee that all the data will be
1685 # sent when using the linger semantics to hard close a socket,
1686 # we send the data and then wait for our peer to release us
1687 # before sending the reset.
1688 data
= DataForRange(first_byte
, possible_rst
)
1689 self
.wfile
.write(data
)
1691 self
.server
.wait_for_download
= True
1692 while self
.server
.wait_for_download
:
1693 self
.server
.handle_request()
1694 l_onoff
= 1 # Linger is active.
1695 l_linger
= 0 # Seconds to linger for.
1696 self
.connection
.setsockopt(socket
.SOL_SOCKET
, socket
.SO_LINGER
,
1697 struct
.pack('ii', l_onoff
, l_linger
))
1699 # Close all duplicates of the underlying socket to force the RST.
1702 self
.connection
.close()
1706 def DefaultResponseHandler(self
):
1707 """This is the catch-all response handler for requests that aren't handled
1708 by one of the special handlers above.
1709 Note that we specify the content-length as without it the https connection
1710 is not closed properly (and the browser keeps expecting data)."""
1712 contents
= "Default response given for path: " + self
.path
1713 self
.send_response(200)
1714 self
.send_header('Content-Type', 'text/html')
1715 self
.send_header('Content-Length', len(contents
))
1717 if (self
.command
!= 'HEAD'):
1718 self
.wfile
.write(contents
)
1721 def RedirectConnectHandler(self
):
1722 """Sends a redirect to the CONNECT request for www.redirect.com. This
1723 response is not specified by the RFC, so the browser should not follow
1726 if (self
.path
.find("www.redirect.com") < 0):
1729 dest
= "http://www.destination.com/foo.js"
1731 self
.send_response(302) # moved temporarily
1732 self
.send_header('Location', dest
)
1733 self
.send_header('Connection', 'close')
1737 def ServerAuthConnectHandler(self
):
1738 """Sends a 401 to the CONNECT request for www.server-auth.com. This
1739 response doesn't make sense because the proxy server cannot request
1740 server authentication."""
1742 if (self
.path
.find("www.server-auth.com") < 0):
1745 challenge
= 'Basic realm="WallyWorld"'
1747 self
.send_response(401) # unauthorized
1748 self
.send_header('WWW-Authenticate', challenge
)
1749 self
.send_header('Connection', 'close')
1753 def DefaultConnectResponseHandler(self
):
1754 """This is the catch-all response handler for CONNECT requests that aren't
1755 handled by one of the special handlers above. Real Web servers respond
1756 with 400 to CONNECT requests."""
1758 contents
= "Your client has issued a malformed or illegal request."
1759 self
.send_response(400) # bad request
1760 self
.send_header('Content-Type', 'text/html')
1761 self
.send_header('Content-Length', len(contents
))
1763 self
.wfile
.write(contents
)
1766 # called by the redirect handling function when there is no parameter
1767 def sendRedirectHelp(self
, redirect_name
):
1768 self
.send_response(200)
1769 self
.send_header('Content-Type', 'text/html')
1771 self
.wfile
.write('<html><body><h1>Error: no redirect destination</h1>')
1772 self
.wfile
.write('Use <pre>%s?http://dest...</pre>' % redirect_name
)
1773 self
.wfile
.write('</body></html>')
1775 # called by chunked handling function
1776 def sendChunkHelp(self
, chunk
):
1777 # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF
1778 self
.wfile
.write('%X\r\n' % len(chunk
))
1779 self
.wfile
.write(chunk
)
1780 self
.wfile
.write('\r\n')
1783 class OCSPHandler(testserver_base
.BasePageHandler
):
1784 def __init__(self
, request
, client_address
, socket_server
):
1785 handlers
= [self
.OCSPResponse
]
1786 self
.ocsp_response
= socket_server
.ocsp_response
1787 testserver_base
.BasePageHandler
.__init
__(self
, request
, client_address
,
1788 socket_server
, [], handlers
, [],
1791 def OCSPResponse(self
):
1792 self
.send_response(200)
1793 self
.send_header('Content-Type', 'application/ocsp-response')
1794 self
.send_header('Content-Length', str(len(self
.ocsp_response
)))
1797 self
.wfile
.write(self
.ocsp_response
)
1800 class TCPEchoHandler(SocketServer
.BaseRequestHandler
):
1801 """The RequestHandler class for TCP echo server.
1803 It is instantiated once per connection to the server, and overrides the
1804 handle() method to implement communication to the client.
1808 """Handles the request from the client and constructs a response."""
1810 data
= self
.request
.recv(65536).strip()
1811 # Verify the "echo request" message received from the client. Send back
1812 # "echo response" message if "echo request" message is valid.
1814 return_data
= echo_message
.GetEchoResponseData(data
)
1820 self
.request
.send(return_data
)
1823 class UDPEchoHandler(SocketServer
.BaseRequestHandler
):
1824 """The RequestHandler class for UDP echo server.
1826 It is instantiated once per connection to the server, and overrides the
1827 handle() method to implement communication to the client.
1831 """Handles the request from the client and constructs a response."""
1833 data
= self
.request
[0].strip()
1834 request_socket
= self
.request
[1]
1835 # Verify the "echo request" message received from the client. Send back
1836 # "echo response" message if "echo request" message is valid.
1838 return_data
= echo_message
.GetEchoResponseData(data
)
1843 request_socket
.sendto(return_data
, self
.client_address
)
1846 class BasicAuthProxyRequestHandler(BaseHTTPServer
.BaseHTTPRequestHandler
):
1847 """A request handler that behaves as a proxy server which requires
1848 basic authentication. Only CONNECT, GET and HEAD is supported for now.
1851 _AUTH_CREDENTIAL
= 'Basic Zm9vOmJhcg==' # foo:bar
1853 def parse_request(self
):
1854 """Overrides parse_request to check credential."""
1856 if not BaseHTTPServer
.BaseHTTPRequestHandler
.parse_request(self
):
1859 auth
= self
.headers
.getheader('Proxy-Authorization')
1860 if auth
!= self
._AUTH
_CREDENTIAL
:
1861 self
.send_response(407)
1862 self
.send_header('Proxy-Authenticate', 'Basic realm="MyRealm1"')
1868 def _start_read_write(self
, sock
):
1870 self
.request
.setblocking(0)
1871 rlist
= [self
.request
, sock
]
1873 ready_sockets
, _unused
, errors
= select
.select(rlist
, [], [])
1875 self
.send_response(500)
1878 for s
in ready_sockets
:
1879 received
= s
.recv(1024)
1880 if len(received
) == 0:
1882 if s
== self
.request
:
1885 other
= self
.request
1886 other
.send(received
)
1888 def _do_common_method(self
):
1889 url
= urlparse
.urlparse(self
.path
)
1892 if url
.scheme
== 'http':
1894 elif url
.scheme
== 'https':
1896 if not url
.hostname
or not port
:
1897 self
.send_response(400)
1901 if len(url
.path
) == 0:
1905 if len(url
.query
) > 0:
1906 path
= '%s?%s' % (url
.path
, url
.query
)
1910 sock
= socket
.create_connection((url
.hostname
, port
))
1911 sock
.send('%s %s %s\r\n' % (
1912 self
.command
, path
, self
.protocol_version
))
1913 for header
in self
.headers
.headers
:
1914 header
= header
.strip()
1915 if (header
.lower().startswith('connection') or
1916 header
.lower().startswith('proxy')):
1918 sock
.send('%s\r\n' % header
)
1920 self
._start
_read
_write
(sock
)
1922 self
.send_response(500)
1925 if sock
is not None:
1928 def do_CONNECT(self
):
1930 pos
= self
.path
.rfind(':')
1931 host
= self
.path
[:pos
]
1932 port
= int(self
.path
[pos
+1:])
1934 self
.send_response(400)
1938 sock
= socket
.create_connection((host
, port
))
1939 self
.send_response(200, 'Connection established')
1941 self
._start
_read
_write
(sock
)
1943 self
.send_response(500)
1949 self
._do
_common
_method
()
1952 self
._do
_common
_method
()
1955 class ServerRunner(testserver_base
.TestServerRunner
):
1956 """TestServerRunner for the net test servers."""
1959 super(ServerRunner
, self
).__init
__()
1960 self
.__ocsp
_server
= None
1962 def __make_data_dir(self
):
1963 if self
.options
.data_dir
:
1964 if not os
.path
.isdir(self
.options
.data_dir
):
1965 raise testserver_base
.OptionError('specified data dir not found: ' +
1966 self
.options
.data_dir
+ ' exiting...')
1967 my_data_dir
= self
.options
.data_dir
1969 # Create the default path to our data dir, relative to the exe dir.
1970 my_data_dir
= os
.path
.join(BASE_DIR
, "..", "..", "..", "..",
1973 #TODO(ibrar): Must use Find* funtion defined in google\tools
1974 #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
1978 def create_server(self
, server_data
):
1979 port
= self
.options
.port
1980 host
= self
.options
.host
1982 # Work around a bug in Mac OS 10.6. Spawning a WebSockets server
1983 # will result in a call to |getaddrinfo|, which fails with "nodename
1984 # nor servname provided" for localhost:0 on 10.6.
1985 if self
.options
.server_type
== SERVER_WEBSOCKET
and \
1986 host
== "localhost" and \
1990 if self
.options
.server_type
== SERVER_HTTP
:
1991 if self
.options
.https
:
1992 pem_cert_and_key
= None
1994 if self
.options
.cert_and_key_file
:
1995 if not os
.path
.isfile(self
.options
.cert_and_key_file
):
1996 raise testserver_base
.OptionError(
1997 'specified server cert file not found: ' +
1998 self
.options
.cert_and_key_file
+ ' exiting...')
1999 pem_cert_and_key
= file(self
.options
.cert_and_key_file
, 'r').read()
2001 # generate a new certificate and run an OCSP server for it.
2002 self
.__ocsp
_server
= OCSPServer((host
, 0), OCSPHandler
)
2003 print ('OCSP server started on %s:%d...' %
2004 (host
, self
.__ocsp
_server
.server_port
))
2008 if self
.options
.ocsp
== 'ok':
2009 ocsp_state
= minica
.OCSP_STATE_GOOD
2010 elif self
.options
.ocsp
== 'revoked':
2011 ocsp_state
= minica
.OCSP_STATE_REVOKED
2012 elif self
.options
.ocsp
== 'invalid':
2013 ocsp_state
= minica
.OCSP_STATE_INVALID
2014 elif self
.options
.ocsp
== 'unauthorized':
2015 ocsp_state
= minica
.OCSP_STATE_UNAUTHORIZED
2016 elif self
.options
.ocsp
== 'unknown':
2017 ocsp_state
= minica
.OCSP_STATE_UNKNOWN
2019 raise testserver_base
.OptionError('unknown OCSP status: ' +
2020 self
.options
.ocsp_status
)
2022 (pem_cert_and_key
, ocsp_der
) = minica
.GenerateCertKeyAndOCSP(
2023 subject
= "127.0.0.1",
2024 ocsp_url
= ("http://%s:%d/ocsp" %
2025 (host
, self
.__ocsp
_server
.server_port
)),
2026 ocsp_state
= ocsp_state
,
2027 serial
= self
.options
.cert_serial
)
2029 if self
.options
.ocsp_server_unavailable
:
2030 # SEQUENCE containing ENUMERATED with value 3 (tryLater).
2031 self
.__ocsp
_server
.ocsp_response
= '30030a0103'.decode('hex')
2033 self
.__ocsp
_server
.ocsp_response
= ocsp_der
2035 for ca_cert
in self
.options
.ssl_client_ca
:
2036 if not os
.path
.isfile(ca_cert
):
2037 raise testserver_base
.OptionError(
2038 'specified trusted client CA file not found: ' + ca_cert
+
2041 stapled_ocsp_response
= None
2042 if self
.options
.staple_ocsp_response
:
2043 stapled_ocsp_response
= ocsp_der
2045 server
= HTTPSServer((host
, port
), TestPageHandler
, pem_cert_and_key
,
2046 self
.options
.ssl_client_auth
,
2047 self
.options
.ssl_client_ca
,
2048 self
.options
.ssl_client_cert_type
,
2049 self
.options
.ssl_bulk_cipher
,
2050 self
.options
.ssl_key_exchange
,
2051 self
.options
.enable_npn
,
2052 self
.options
.record_resume
,
2053 self
.options
.tls_intolerant
,
2054 self
.options
.tls_intolerance_type
,
2055 self
.options
.signed_cert_timestamps_tls_ext
.decode(
2057 self
.options
.fallback_scsv
,
2058 stapled_ocsp_response
,
2059 self
.options
.alert_after_handshake
)
2060 print 'HTTPS server started on https://%s:%d...' % \
2061 (host
, server
.server_port
)
2063 server
= HTTPServer((host
, port
), TestPageHandler
)
2064 print 'HTTP server started on http://%s:%d...' % \
2065 (host
, server
.server_port
)
2067 server
.data_dir
= self
.__make
_data
_dir
()
2068 server
.file_root_url
= self
.options
.file_root_url
2069 server_data
['port'] = server
.server_port
2070 elif self
.options
.server_type
== SERVER_WEBSOCKET
:
2071 # Launch pywebsocket via WebSocketServer.
2072 logger
= logging
.getLogger()
2073 logger
.addHandler(logging
.StreamHandler())
2074 # TODO(toyoshim): Remove following os.chdir. Currently this operation
2075 # is required to work correctly. It should be fixed from pywebsocket side.
2076 os
.chdir(self
.__make
_data
_dir
())
2077 websocket_options
= WebSocketOptions(host
, port
, '.')
2079 if self
.options
.cert_and_key_file
:
2081 websocket_options
.use_tls
= True
2082 websocket_options
.private_key
= self
.options
.cert_and_key_file
2083 websocket_options
.certificate
= self
.options
.cert_and_key_file
2084 if self
.options
.ssl_client_auth
:
2085 websocket_options
.tls_client_cert_optional
= False
2086 websocket_options
.tls_client_auth
= True
2087 if len(self
.options
.ssl_client_ca
) != 1:
2088 raise testserver_base
.OptionError(
2089 'one trusted client CA file should be specified')
2090 if not os
.path
.isfile(self
.options
.ssl_client_ca
[0]):
2091 raise testserver_base
.OptionError(
2092 'specified trusted client CA file not found: ' +
2093 self
.options
.ssl_client_ca
[0] + ' exiting...')
2094 websocket_options
.tls_client_ca
= self
.options
.ssl_client_ca
[0]
2095 print 'Trying to start websocket server on %s://%s:%d...' % \
2096 (scheme
, websocket_options
.server_host
, websocket_options
.port
)
2097 server
= WebSocketServer(websocket_options
)
2098 print 'WebSocket server started on %s://%s:%d...' % \
2099 (scheme
, host
, server
.server_port
)
2100 server_data
['port'] = server
.server_port
2101 websocket_options
.use_basic_auth
= self
.options
.ws_basic_auth
2102 elif self
.options
.server_type
== SERVER_TCP_ECHO
:
2103 # Used for generating the key (randomly) that encodes the "echo request"
2106 server
= TCPEchoServer((host
, port
), TCPEchoHandler
)
2107 print 'Echo TCP server started on port %d...' % server
.server_port
2108 server_data
['port'] = server
.server_port
2109 elif self
.options
.server_type
== SERVER_UDP_ECHO
:
2110 # Used for generating the key (randomly) that encodes the "echo request"
2113 server
= UDPEchoServer((host
, port
), UDPEchoHandler
)
2114 print 'Echo UDP server started on port %d...' % server
.server_port
2115 server_data
['port'] = server
.server_port
2116 elif self
.options
.server_type
== SERVER_BASIC_AUTH_PROXY
:
2117 server
= HTTPServer((host
, port
), BasicAuthProxyRequestHandler
)
2118 print 'BasicAuthProxy server started on port %d...' % server
.server_port
2119 server_data
['port'] = server
.server_port
2120 elif self
.options
.server_type
== SERVER_FTP
:
2121 my_data_dir
= self
.__make
_data
_dir
()
2123 # Instantiate a dummy authorizer for managing 'virtual' users
2124 authorizer
= pyftpdlib
.ftpserver
.DummyAuthorizer()
2126 # Define a new user having full r/w permissions
2127 authorizer
.add_user('chrome', 'chrome', my_data_dir
, perm
='elradfmw')
2129 # Define a read-only anonymous user unless disabled
2130 if not self
.options
.no_anonymous_ftp_user
:
2131 authorizer
.add_anonymous(my_data_dir
)
2133 # Instantiate FTP handler class
2134 ftp_handler
= pyftpdlib
.ftpserver
.FTPHandler
2135 ftp_handler
.authorizer
= authorizer
2137 # Define a customized banner (string returned when client connects)
2138 ftp_handler
.banner
= ("pyftpdlib %s based ftpd ready." %
2139 pyftpdlib
.ftpserver
.__ver
__)
2141 # Instantiate FTP server class and listen to address:port
2142 server
= pyftpdlib
.ftpserver
.FTPServer((host
, port
), ftp_handler
)
2143 server_data
['port'] = server
.socket
.getsockname()[1]
2144 print 'FTP server started on port %d...' % server_data
['port']
2146 raise testserver_base
.OptionError('unknown server type' +
2147 self
.options
.server_type
)
2151 def run_server(self
):
2152 if self
.__ocsp
_server
:
2153 self
.__ocsp
_server
.serve_forever_on_thread()
2155 testserver_base
.TestServerRunner
.run_server(self
)
2157 if self
.__ocsp
_server
:
2158 self
.__ocsp
_server
.stop_serving()
2160 def add_options(self
):
2161 testserver_base
.TestServerRunner
.add_options(self
)
2162 self
.option_parser
.add_option('-f', '--ftp', action
='store_const',
2163 const
=SERVER_FTP
, default
=SERVER_HTTP
,
2165 help='start up an FTP server.')
2166 self
.option_parser
.add_option('--tcp-echo', action
='store_const',
2167 const
=SERVER_TCP_ECHO
, default
=SERVER_HTTP
,
2169 help='start up a tcp echo server.')
2170 self
.option_parser
.add_option('--udp-echo', action
='store_const',
2171 const
=SERVER_UDP_ECHO
, default
=SERVER_HTTP
,
2173 help='start up a udp echo server.')
2174 self
.option_parser
.add_option('--basic-auth-proxy', action
='store_const',
2175 const
=SERVER_BASIC_AUTH_PROXY
,
2176 default
=SERVER_HTTP
, dest
='server_type',
2177 help='start up a proxy server which requires '
2178 'basic authentication.')
2179 self
.option_parser
.add_option('--websocket', action
='store_const',
2180 const
=SERVER_WEBSOCKET
, default
=SERVER_HTTP
,
2182 help='start up a WebSocket server.')
2183 self
.option_parser
.add_option('--https', action
='store_true',
2184 dest
='https', help='Specify that https '
2186 self
.option_parser
.add_option('--cert-and-key-file',
2187 dest
='cert_and_key_file', help='specify the '
2188 'path to the file containing the certificate '
2189 'and private key for the server in PEM '
2191 self
.option_parser
.add_option('--ocsp', dest
='ocsp', default
='ok',
2192 help='The type of OCSP response generated '
2193 'for the automatically generated '
2194 'certificate. One of [ok,revoked,invalid]')
2195 self
.option_parser
.add_option('--cert-serial', dest
='cert_serial',
2196 default
=0, type=int,
2197 help='If non-zero then the generated '
2198 'certificate will have this serial number')
2199 self
.option_parser
.add_option('--tls-intolerant', dest
='tls_intolerant',
2200 default
='0', type='int',
2201 help='If nonzero, certain TLS connections '
2202 'will be aborted in order to test version '
2203 'fallback. 1 means all TLS versions will be '
2204 'aborted. 2 means TLS 1.1 or higher will be '
2205 'aborted. 3 means TLS 1.2 or higher will be '
2207 self
.option_parser
.add_option('--tls-intolerance-type',
2208 dest
='tls_intolerance_type',
2210 help='Controls how the server reacts to a '
2211 'TLS version it is intolerant to. Valid '
2212 'values are "alert", "close", and "reset".')
2213 self
.option_parser
.add_option('--signed-cert-timestamps-tls-ext',
2214 dest
='signed_cert_timestamps_tls_ext',
2216 help='Base64 encoded SCT list. If set, '
2217 'server will respond with a '
2218 'signed_certificate_timestamp TLS extension '
2219 'whenever the client supports it.')
2220 self
.option_parser
.add_option('--fallback-scsv', dest
='fallback_scsv',
2221 default
=False, const
=True,
2222 action
='store_const',
2223 help='If given, TLS_FALLBACK_SCSV support '
2224 'will be enabled. This causes the server to '
2225 'reject fallback connections from compatible '
2226 'clients (e.g. Chrome).')
2227 self
.option_parser
.add_option('--staple-ocsp-response',
2228 dest
='staple_ocsp_response',
2229 default
=False, action
='store_true',
2230 help='If set, server will staple the OCSP '
2231 'response whenever OCSP is on and the client '
2232 'supports OCSP stapling.')
2233 self
.option_parser
.add_option('--https-record-resume',
2234 dest
='record_resume', const
=True,
2235 default
=False, action
='store_const',
2236 help='Record resumption cache events rather '
2237 'than resuming as normal. Allows the use of '
2238 'the /ssl-session-cache request')
2239 self
.option_parser
.add_option('--ssl-client-auth', action
='store_true',
2240 help='Require SSL client auth on every '
2242 self
.option_parser
.add_option('--ssl-client-ca', action
='append',
2243 default
=[], help='Specify that the client '
2244 'certificate request should include the CA '
2245 'named in the subject of the DER-encoded '
2246 'certificate contained in the specified '
2247 'file. This option may appear multiple '
2248 'times, indicating multiple CA names should '
2249 'be sent in the request.')
2250 self
.option_parser
.add_option('--ssl-client-cert-type', action
='append',
2251 default
=[], help='Specify that the client '
2252 'certificate request should include the '
2253 'specified certificate_type value. This '
2254 'option may appear multiple times, '
2255 'indicating multiple values should be send '
2256 'in the request. Valid values are '
2257 '"rsa_sign", "dss_sign", and "ecdsa_sign". '
2258 'If omitted, "rsa_sign" will be used.')
2259 self
.option_parser
.add_option('--ssl-bulk-cipher', action
='append',
2260 help='Specify the bulk encryption '
2261 'algorithm(s) that will be accepted by the '
2262 'SSL server. Valid values are "aes128gcm", '
2263 '"aes256", "aes128", "3des", "rc4". If '
2264 'omitted, all algorithms will be used. This '
2265 'option may appear multiple times, '
2266 'indicating multiple algorithms should be '
2268 self
.option_parser
.add_option('--ssl-key-exchange', action
='append',
2269 help='Specify the key exchange algorithm(s)'
2270 'that will be accepted by the SSL server. '
2271 'Valid values are "rsa", "dhe_rsa", '
2272 '"ecdhe_rsa". If omitted, all algorithms '
2273 'will be used. This option may appear '
2274 'multiple times, indicating multiple '
2275 'algorithms should be enabled.');
2276 # TODO(davidben): Add ALPN support to tlslite.
2277 self
.option_parser
.add_option('--enable-npn', dest
='enable_npn',
2278 default
=False, const
=True,
2279 action
='store_const',
2280 help='Enable server support for the NPN '
2281 'extension. The server will advertise '
2282 'support for exactly one protocol, http/1.1')
2283 self
.option_parser
.add_option('--file-root-url', default
='/files/',
2284 help='Specify a root URL for files served.')
2285 # TODO(ricea): Generalize this to support basic auth for HTTP too.
2286 self
.option_parser
.add_option('--ws-basic-auth', action
='store_true',
2287 dest
='ws_basic_auth',
2288 help='Enable basic-auth for WebSocket')
2289 self
.option_parser
.add_option('--ocsp-server-unavailable',
2290 dest
='ocsp_server_unavailable',
2291 default
=False, action
='store_true',
2292 help='If set, the OCSP server will return '
2293 'a tryLater status rather than the actual '
2295 self
.option_parser
.add_option('--alert-after-handshake',
2296 dest
='alert_after_handshake',
2297 default
=False, action
='store_true',
2298 help='If set, the server will send a fatal '
2299 'alert immediately after the handshake.')
2300 self
.option_parser
.add_option('--no-anonymous-ftp-user',
2301 dest
='no_anonymous_ftp_user',
2302 default
=False, action
='store_true',
2303 help='If set, the FTP server will not create '
2304 'an anonymous user.')
2307 if __name__
== '__main__':
2308 sys
.exit(ServerRunner().main())