Home | History | Annotate | Download | only in testserver
      1 #!/usr/bin/python2.4
      2 # Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 """This is a simple HTTP server used for testing Chrome.
      7 
      8 It supports several test URLs, as specified by the handlers in TestPageHandler.
      9 It defaults to living on localhost:8888.
     10 It can use https if you specify the flag --https=CERT where CERT is the path
     11 to a pem file containing the certificate and private key that should be used.
     12 To shut it down properly, visit localhost:8888/kill.
     13 """
     14 
     15 import base64
     16 import BaseHTTPServer
     17 import cgi
     18 import optparse
     19 import os
     20 import re
     21 import shutil
     22 import SocketServer
     23 import sys
     24 import time
     25 import tlslite
     26 import tlslite.api
     27 import pyftpdlib.ftpserver
     28 
     29 try:
     30   import hashlib
     31   _new_md5 = hashlib.md5
     32 except ImportError:
     33   import md5
     34   _new_md5 = md5.new
     35 
     36 SERVER_HTTP = 0
     37 SERVER_FTP = 1
     38 
     39 debug_output = sys.stderr
     40 def debug(str):
     41   debug_output.write(str + "\n")
     42   debug_output.flush()
     43 
     44 class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
     45   """This is a specialization of of BaseHTTPServer to allow it
     46   to be exited cleanly (by setting its "stop" member to True)."""
     47 
     48   def serve_forever(self):
     49     self.stop = False
     50     self.nonce = None
     51     while not self.stop:
     52       self.handle_request()
     53     self.socket.close()
     54 
     55 class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer):
     56   """This is a specialization of StoppableHTTPerver that add https support."""
     57 
     58   def __init__(self, server_address, request_hander_class, cert_path):
     59     s = open(cert_path).read()
     60     x509 = tlslite.api.X509()
     61     x509.parse(s)
     62     self.cert_chain = tlslite.api.X509CertChain([x509])
     63     s = open(cert_path).read()
     64     self.private_key = tlslite.api.parsePEMKey(s, private=True)
     65 
     66     self.session_cache = tlslite.api.SessionCache()
     67     StoppableHTTPServer.__init__(self, server_address, request_hander_class)
     68 
     69   def handshake(self, tlsConnection):
     70     """Creates the SSL connection."""
     71     try:
     72       tlsConnection.handshakeServer(certChain=self.cert_chain,
     73                                     privateKey=self.private_key,
     74                                     sessionCache=self.session_cache)
     75       tlsConnection.ignoreAbruptClose = True
     76       return True
     77     except tlslite.api.TLSError, error:
     78       print "Handshake failure:", str(error)
     79       return False
     80 
     81 class ForkingHTTPServer(SocketServer.ForkingMixIn, StoppableHTTPServer):
     82   """This is a specialization of of StoppableHTTPServer which serves each
     83   request in a separate process"""
     84   pass
     85 
     86 class ForkingHTTPSServer(SocketServer.ForkingMixIn, HTTPSServer):
     87   """This is a specialization of of HTTPSServer which serves each
     88   request in a separate process"""
     89   pass
     90 
     91 class TestPageHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     92 
     93   def __init__(self, request, client_address, socket_server):
     94     self._connect_handlers = [
     95       self.RedirectConnectHandler,
     96       self.ServerAuthConnectHandler,
     97       self.DefaultConnectResponseHandler]
     98     self._get_handlers = [
     99       self.KillHandler,
    100       self.NoCacheMaxAgeTimeHandler,
    101       self.NoCacheTimeHandler,
    102       self.CacheTimeHandler,
    103       self.CacheExpiresHandler,
    104       self.CacheProxyRevalidateHandler,
    105       self.CachePrivateHandler,
    106       self.CachePublicHandler,
    107       self.CacheSMaxAgeHandler,
    108       self.CacheMustRevalidateHandler,
    109       self.CacheMustRevalidateMaxAgeHandler,
    110       self.CacheNoStoreHandler,
    111       self.CacheNoStoreMaxAgeHandler,
    112       self.CacheNoTransformHandler,
    113       self.DownloadHandler,
    114       self.DownloadFinishHandler,
    115       self.EchoHeader,
    116       self.EchoHeaderOverride,
    117       self.EchoAllHandler,
    118       self.FileHandler,
    119       self.RealFileWithCommonHeaderHandler,
    120       self.RealBZ2FileWithCommonHeaderHandler,
    121       self.SetCookieHandler,
    122       self.AuthBasicHandler,
    123       self.AuthDigestHandler,
    124       self.SlowServerHandler,
    125       self.ContentTypeHandler,
    126       self.ServerRedirectHandler,
    127       self.ClientRedirectHandler,
    128       self.DefaultResponseHandler]
    129     self._post_handlers = [
    130       self.WriteFile,
    131       self.EchoTitleHandler,
    132       self.EchoAllHandler,
    133       self.EchoHandler] + self._get_handlers
    134     self._put_handlers = [
    135       self.WriteFile,
    136       self.EchoTitleHandler,
    137       self.EchoAllHandler,
    138       self.EchoHandler] + self._get_handlers
    139 
    140     self._mime_types = {
    141       'gif': 'image/gif',
    142       'jpeg' : 'image/jpeg',
    143       'jpg' : 'image/jpeg',
    144       'xml' : 'text/xml'
    145     }
    146     self._default_mime_type = 'text/html'
    147 
    148     BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, request,
    149                                                    client_address,
    150                                                    socket_server)
    151 
    152   def _ShouldHandleRequest(self, handler_name):
    153     """Determines if the path can be handled by the handler.
    154 
    155     We consider a handler valid if the path begins with the
    156     handler name. It can optionally be followed by "?*", "/*".
    157     """
    158 
    159     pattern = re.compile('%s($|\?|/).*' % handler_name)
    160     return pattern.match(self.path)
    161 
    162   def GetMIMETypeFromName(self, file_name):
    163     """Returns the mime type for the specified file_name. So far it only looks
    164     at the file extension."""
    165 
    166     (shortname, extension) = os.path.splitext(file_name)
    167     if len(extension) == 0:
    168       # no extension.
    169       return self._default_mime_type
    170 
    171     # extension starts with a dot, so we need to remove it
    172     return self._mime_types.get(extension[1:], self._default_mime_type)
    173 
    174   def KillHandler(self):
    175     """This request handler kills the server, for use when we're done"
    176     with the a particular test."""
    177 
    178     if (self.path.find("kill") < 0):
    179       return False
    180 
    181     self.send_response(200)
    182     self.send_header('Content-type', 'text/html')
    183     self.send_header('Cache-Control', 'max-age=0')
    184     self.end_headers()
    185     if options.never_die:
    186       self.wfile.write('I cannot die!! BWAHAHA')
    187     else:
    188       self.wfile.write('Goodbye cruel world!')
    189       self.server.stop = True
    190 
    191     return True
    192 
    193   def NoCacheMaxAgeTimeHandler(self):
    194     """This request handler yields a page with the title set to the current
    195     system time, and no caching requested."""
    196 
    197     if not self._ShouldHandleRequest("/nocachetime/maxage"):
    198       return False
    199 
    200     self.send_response(200)
    201     self.send_header('Cache-Control', 'max-age=0')
    202     self.send_header('Content-type', 'text/html')
    203     self.end_headers()
    204 
    205     self.wfile.write('<html><head><title>%s</title></head></html>' %
    206                      time.time())
    207 
    208     return True
    209 
    210   def NoCacheTimeHandler(self):
    211     """This request handler yields a page with the title set to the current
    212     system time, and no caching requested."""
    213 
    214     if not self._ShouldHandleRequest("/nocachetime"):
    215       return False
    216 
    217     self.send_response(200)
    218     self.send_header('Cache-Control', 'no-cache')
    219     self.send_header('Content-type', 'text/html')
    220     self.end_headers()
    221 
    222     self.wfile.write('<html><head><title>%s</title></head></html>' %
    223                      time.time())
    224 
    225     return True
    226 
    227   def CacheTimeHandler(self):
    228     """This request handler yields a page with the title set to the current
    229     system time, and allows caching for one minute."""
    230 
    231     if not self._ShouldHandleRequest("/cachetime"):
    232       return False
    233 
    234     self.send_response(200)
    235     self.send_header('Cache-Control', 'max-age=60')
    236     self.send_header('Content-type', 'text/html')
    237     self.end_headers()
    238 
    239     self.wfile.write('<html><head><title>%s</title></head></html>' %
    240                      time.time())
    241 
    242     return True
    243 
    244   def CacheExpiresHandler(self):
    245     """This request handler yields a page with the title set to the current
    246     system time, and set the page to expire on 1 Jan 2099."""
    247 
    248     if not self._ShouldHandleRequest("/cache/expires"):
    249       return False
    250 
    251     self.send_response(200)
    252     self.send_header('Expires', 'Thu, 1 Jan 2099 00:00:00 GMT')
    253     self.send_header('Content-type', 'text/html')
    254     self.end_headers()
    255 
    256     self.wfile.write('<html><head><title>%s</title></head></html>' %
    257                      time.time())
    258 
    259     return True
    260 
    261   def CacheProxyRevalidateHandler(self):
    262     """This request handler yields a page with the title set to the current
    263     system time, and allows caching for 60 seconds"""
    264 
    265     if not self._ShouldHandleRequest("/cache/proxy-revalidate"):
    266       return False
    267 
    268     self.send_response(200)
    269     self.send_header('Content-type', 'text/html')
    270     self.send_header('Cache-Control', 'max-age=60, proxy-revalidate')
    271     self.end_headers()
    272 
    273     self.wfile.write('<html><head><title>%s</title></head></html>' %
    274                      time.time())
    275 
    276     return True
    277 
    278   def CachePrivateHandler(self):
    279     """This request handler yields a page with the title set to the current
    280     system time, and allows caching for 5 seconds."""
    281 
    282     if not self._ShouldHandleRequest("/cache/private"):
    283       return False
    284 
    285     self.send_response(200)
    286     self.send_header('Content-type', 'text/html')
    287     self.send_header('Cache-Control', 'max-age=3, private')
    288     self.end_headers()
    289 
    290     self.wfile.write('<html><head><title>%s</title></head></html>' %
    291                      time.time())
    292 
    293     return True
    294 
    295   def CachePublicHandler(self):
    296     """This request handler yields a page with the title set to the current
    297     system time, and allows caching for 5 seconds."""
    298 
    299     if not self._ShouldHandleRequest("/cache/public"):
    300       return False
    301 
    302     self.send_response(200)
    303     self.send_header('Content-type', 'text/html')
    304     self.send_header('Cache-Control', 'max-age=3, public')
    305     self.end_headers()
    306 
    307     self.wfile.write('<html><head><title>%s</title></head></html>' %
    308                      time.time())
    309 
    310     return True
    311 
    312   def CacheSMaxAgeHandler(self):
    313     """This request handler yields a page with the title set to the current
    314     system time, and does not allow for caching."""
    315 
    316     if not self._ShouldHandleRequest("/cache/s-maxage"):
    317       return False
    318 
    319     self.send_response(200)
    320     self.send_header('Content-type', 'text/html')
    321     self.send_header('Cache-Control', 'public, s-maxage = 60, max-age = 0')
    322     self.end_headers()
    323 
    324     self.wfile.write('<html><head><title>%s</title></head></html>' %
    325                      time.time())
    326 
    327     return True
    328 
    329   def CacheMustRevalidateHandler(self):
    330     """This request handler yields a page with the title set to the current
    331     system time, and does not allow caching."""
    332 
    333     if not self._ShouldHandleRequest("/cache/must-revalidate"):
    334       return False
    335 
    336     self.send_response(200)
    337     self.send_header('Content-type', 'text/html')
    338     self.send_header('Cache-Control', 'must-revalidate')
    339     self.end_headers()
    340 
    341     self.wfile.write('<html><head><title>%s</title></head></html>' %
    342                      time.time())
    343 
    344     return True
    345 
    346   def CacheMustRevalidateMaxAgeHandler(self):
    347     """This request handler yields a page with the title set to the current
    348     system time, and does not allow caching event though max-age of 60
    349     seconds is specified."""
    350 
    351     if not self._ShouldHandleRequest("/cache/must-revalidate/max-age"):
    352       return False
    353 
    354     self.send_response(200)
    355     self.send_header('Content-type', 'text/html')
    356     self.send_header('Cache-Control', 'max-age=60, must-revalidate')
    357     self.end_headers()
    358 
    359     self.wfile.write('<html><head><title>%s</title></head></html>' %
    360                      time.time())
    361 
    362     return True
    363 
    364   def CacheNoStoreHandler(self):
    365     """This request handler yields a page with the title set to the current
    366     system time, and does not allow the page to be stored."""
    367 
    368     if not self._ShouldHandleRequest("/cache/no-store"):
    369       return False
    370 
    371     self.send_response(200)
    372     self.send_header('Content-type', 'text/html')
    373     self.send_header('Cache-Control', 'no-store')
    374     self.end_headers()
    375 
    376     self.wfile.write('<html><head><title>%s</title></head></html>' %
    377                      time.time())
    378 
    379     return True
    380 
    381   def CacheNoStoreMaxAgeHandler(self):
    382     """This request handler yields a page with the title set to the current
    383     system time, and does not allow the page to be stored even though max-age
    384     of 60 seconds is specified."""
    385 
    386     if not self._ShouldHandleRequest("/cache/no-store/max-age"):
    387       return False
    388 
    389     self.send_response(200)
    390     self.send_header('Content-type', 'text/html')
    391     self.send_header('Cache-Control', 'max-age=60, no-store')
    392     self.end_headers()
    393 
    394     self.wfile.write('<html><head><title>%s</title></head></html>' %
    395                      time.time())
    396 
    397     return True
    398 
    399 
    400   def CacheNoTransformHandler(self):
    401     """This request handler yields a page with the title set to the current
    402     system time, and does not allow the content to transformed during
    403     user-agent caching"""
    404 
    405     if not self._ShouldHandleRequest("/cache/no-transform"):
    406       return False
    407 
    408     self.send_response(200)
    409     self.send_header('Content-type', 'text/html')
    410     self.send_header('Cache-Control', 'no-transform')
    411     self.end_headers()
    412 
    413     self.wfile.write('<html><head><title>%s</title></head></html>' %
    414                      time.time())
    415 
    416     return True
    417 
    418   def EchoHeader(self):
    419     """This handler echoes back the value of a specific request header."""
    420     """The only difference between this function and the EchoHeaderOverride"""
    421     """function is in the parameter being passed to the helper function"""
    422     return self.EchoHeaderHelper("/echoheader")
    423 
    424   def EchoHeaderOverride(self):
    425     """This handler echoes back the value of a specific request header."""
    426     """The UrlRequest unit tests also execute for ChromeFrame which uses"""
    427     """IE to issue HTTP requests using the host network stack."""
    428     """The Accept and Charset tests which expect the server to echo back"""
    429     """the corresponding headers fail here as IE returns cached responses"""
    430     """The EchoHeaderOverride parameter is an easy way to ensure that IE"""
    431     """treats this request as a new request and does not cache it."""
    432     return self.EchoHeaderHelper("/echoheaderoverride")
    433 
    434   def EchoHeaderHelper(self, echo_header):
    435     """This function echoes back the value of the request header passed in."""
    436     if not self._ShouldHandleRequest(echo_header):
    437       return False
    438 
    439     query_char = self.path.find('?')
    440     if query_char != -1:
    441       header_name = self.path[query_char+1:]
    442 
    443     self.send_response(200)
    444     self.send_header('Content-type', 'text/plain')
    445     self.send_header('Cache-control', 'max-age=60000')
    446     # insert a vary header to properly indicate that the cachability of this
    447     # request is subject to value of the request header being echoed.
    448     if len(header_name) > 0:
    449       self.send_header('Vary', header_name)
    450     self.end_headers()
    451 
    452     if len(header_name) > 0:
    453       self.wfile.write(self.headers.getheader(header_name))
    454 
    455     return True
    456 
    457   def EchoHandler(self):
    458     """This handler just echoes back the payload of the request, for testing
    459     form submission."""
    460 
    461     if not self._ShouldHandleRequest("/echo"):
    462       return False
    463 
    464     self.send_response(200)
    465     self.send_header('Content-type', 'text/html')
    466     self.end_headers()
    467     length = int(self.headers.getheader('content-length'))
    468     request = self.rfile.read(length)
    469     self.wfile.write(request)
    470     return True
    471 
    472   def WriteFile(self):
    473     """This is handler dumps the content of POST/PUT request to a disk file
    474     into the data_dir/dump. Sub-directories are not supported."""
    475 
    476     prefix='/writefile/'
    477     if not self.path.startswith(prefix):
    478       return False
    479 
    480     file_name = self.path[len(prefix):]
    481 
    482     # do not allow fancy chars in file name
    483     re.sub('[^a-zA-Z0-9_.-]+', '', file_name)
    484     if len(file_name) and file_name[0] != '.':
    485       path = os.path.join(self.server.data_dir, 'dump', file_name);
    486       length = int(self.headers.getheader('content-length'))
    487       request = self.rfile.read(length)
    488       f = open(path, "wb")
    489       f.write(request);
    490       f.close()
    491 
    492     self.send_response(200)
    493     self.send_header('Content-type', 'text/html')
    494     self.end_headers()
    495     self.wfile.write('<html>%s</html>' % file_name)
    496     return True
    497 
    498   def EchoTitleHandler(self):
    499     """This handler is like Echo, but sets the page title to the request."""
    500 
    501     if not self._ShouldHandleRequest("/echotitle"):
    502       return False
    503 
    504     self.send_response(200)
    505     self.send_header('Content-type', 'text/html')
    506     self.end_headers()
    507     length = int(self.headers.getheader('content-length'))
    508     request = self.rfile.read(length)
    509     self.wfile.write('<html><head><title>')
    510     self.wfile.write(request)
    511     self.wfile.write('</title></head></html>')
    512     return True
    513 
    514   def EchoAllHandler(self):
    515     """This handler yields a (more) human-readable page listing information
    516     about the request header & contents."""
    517 
    518     if not self._ShouldHandleRequest("/echoall"):
    519       return False
    520 
    521     self.send_response(200)
    522     self.send_header('Content-type', 'text/html')
    523     self.end_headers()
    524     self.wfile.write('<html><head><style>'
    525       'pre { border: 1px solid black; margin: 5px; padding: 5px }'
    526       '</style></head><body>'
    527       '<div style="float: right">'
    528       '<a href="http://localhost:8888/echo">back to referring page</a></div>'
    529       '<h1>Request Body:</h1><pre>')
    530 
    531     if self.command == 'POST' or self.command == 'PUT':
    532       length = int(self.headers.getheader('content-length'))
    533       qs = self.rfile.read(length)
    534       params = cgi.parse_qs(qs, keep_blank_values=1)
    535 
    536       for param in params:
    537         self.wfile.write('%s=%s\n' % (param, params[param][0]))
    538 
    539     self.wfile.write('</pre>')
    540 
    541     self.wfile.write('<h1>Request Headers:</h1><pre>%s</pre>' % self.headers)
    542 
    543     self.wfile.write('</body></html>')
    544     return True
    545 
    546   def DownloadHandler(self):
    547     """This handler sends a downloadable file with or without reporting
    548     the size (6K)."""
    549 
    550     if self.path.startswith("/download-unknown-size"):
    551       send_length = False
    552     elif self.path.startswith("/download-known-size"):
    553       send_length = True
    554     else:
    555       return False
    556 
    557     #
    558     # The test which uses this functionality is attempting to send
    559     # small chunks of data to the client.  Use a fairly large buffer
    560     # so that we'll fill chrome's IO buffer enough to force it to
    561     # actually write the data.
    562     # See also the comments in the client-side of this test in
    563     # download_uitest.cc
    564     #
    565     size_chunk1 = 35*1024
    566     size_chunk2 = 10*1024
    567 
    568     self.send_response(200)
    569     self.send_header('Content-type', 'application/octet-stream')
    570     self.send_header('Cache-Control', 'max-age=0')
    571     if send_length:
    572       self.send_header('Content-Length', size_chunk1 + size_chunk2)
    573     self.end_headers()
    574 
    575     # First chunk of data:
    576     self.wfile.write("*" * size_chunk1)
    577     self.wfile.flush()
    578 
    579     # handle requests until one of them clears this flag.
    580     self.server.waitForDownload = True
    581     while self.server.waitForDownload:
    582       self.server.handle_request()
    583 
    584     # Second chunk of data:
    585     self.wfile.write("*" * size_chunk2)
    586     return True
    587 
    588   def DownloadFinishHandler(self):
    589     """This handler just tells the server to finish the current download."""
    590 
    591     if not self._ShouldHandleRequest("/download-finish"):
    592       return False
    593 
    594     self.server.waitForDownload = False
    595     self.send_response(200)
    596     self.send_header('Content-type', 'text/html')
    597     self.send_header('Cache-Control', 'max-age=0')
    598     self.end_headers()
    599     return True
    600 
    601   def FileHandler(self):
    602     """This handler sends the contents of the requested file.  Wow, it's like
    603     a real webserver!"""
    604 
    605     prefix = self.server.file_root_url
    606     if not self.path.startswith(prefix):
    607       return False
    608 
    609     # Consume a request body if present.
    610     if self.command == 'POST' or self.command == 'PUT' :
    611       self.rfile.read(int(self.headers.getheader('content-length')))
    612 
    613     file = self.path[len(prefix):]
    614     if file.find('?') > -1:
    615       # Ignore the query parameters entirely.
    616       url, querystring = file.split('?')
    617     else:
    618       url = file
    619     entries = url.split('/')
    620     path = os.path.join(self.server.data_dir, *entries)
    621     if os.path.isdir(path):
    622       path = os.path.join(path, 'index.html')
    623 
    624     if not os.path.isfile(path):
    625       print "File not found " + file + " full path:" + path
    626       self.send_error(404)
    627       return True
    628 
    629     f = open(path, "rb")
    630     data = f.read()
    631     f.close()
    632 
    633     # If file.mock-http-headers exists, it contains the headers we
    634     # should send.  Read them in and parse them.
    635     headers_path = path + '.mock-http-headers'
    636     if os.path.isfile(headers_path):
    637       f = open(headers_path, "r")
    638 
    639       # "HTTP/1.1 200 OK"
    640       response = f.readline()
    641       status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0]
    642       self.send_response(int(status_code))
    643 
    644       for line in f:
    645         header_values = re.findall('(\S+):\s*(.*)', line)
    646         if len(header_values) > 0:
    647           # "name: value"
    648           name, value = header_values[0]
    649           self.send_header(name, value)
    650       f.close()
    651     else:
    652       # Could be more generic once we support mime-type sniffing, but for
    653       # now we need to set it explicitly.
    654       self.send_response(200)
    655       self.send_header('Content-type', self.GetMIMETypeFromName(file))
    656       self.send_header('Content-Length', len(data))
    657     self.end_headers()
    658 
    659     self.wfile.write(data)
    660 
    661     return True
    662 
    663   def RealFileWithCommonHeaderHandler(self):
    664     """This handler sends the contents of the requested file without the pseudo
    665     http head!"""
    666 
    667     prefix='/realfiles/'
    668     if not self.path.startswith(prefix):
    669       return False
    670 
    671     file = self.path[len(prefix):]
    672     path = os.path.join(self.server.data_dir, file)
    673 
    674     try:
    675       f = open(path, "rb")
    676       data = f.read()
    677       f.close()
    678 
    679       # just simply set the MIME as octal stream
    680       self.send_response(200)
    681       self.send_header('Content-type', 'application/octet-stream')
    682       self.end_headers()
    683 
    684       self.wfile.write(data)
    685     except:
    686       self.send_error(404)
    687 
    688     return True
    689 
    690   def RealBZ2FileWithCommonHeaderHandler(self):
    691     """This handler sends the bzip2 contents of the requested file with
    692      corresponding Content-Encoding field in http head!"""
    693 
    694     prefix='/realbz2files/'
    695     if not self.path.startswith(prefix):
    696       return False
    697 
    698     parts = self.path.split('?')
    699     file = parts[0][len(prefix):]
    700     path = os.path.join(self.server.data_dir, file) + '.bz2'
    701 
    702     if len(parts) > 1:
    703       options = parts[1]
    704     else:
    705       options = ''
    706 
    707     try:
    708       self.send_response(200)
    709       accept_encoding = self.headers.get("Accept-Encoding")
    710       if accept_encoding.find("bzip2") != -1:
    711         f = open(path, "rb")
    712         data = f.read()
    713         f.close()
    714         self.send_header('Content-Encoding', 'bzip2')
    715         self.send_header('Content-type', 'application/x-bzip2')
    716         self.end_headers()
    717         if options == 'incremental-header':
    718           self.wfile.write(data[:1])
    719           self.wfile.flush()
    720           time.sleep(1.0)
    721           self.wfile.write(data[1:])
    722         else:
    723           self.wfile.write(data)
    724       else:
    725         """client do not support bzip2 format, send pseudo content
    726         """
    727         self.send_header('Content-type', 'text/html; charset=ISO-8859-1')
    728         self.end_headers()
    729         self.wfile.write("you do not support bzip2 encoding")
    730     except:
    731       self.send_error(404)
    732 
    733     return True
    734 
    735   def SetCookieHandler(self):
    736     """This handler just sets a cookie, for testing cookie handling."""
    737 
    738     if not self._ShouldHandleRequest("/set-cookie"):
    739       return False
    740 
    741     query_char = self.path.find('?')
    742     if query_char != -1:
    743       cookie_values = self.path[query_char + 1:].split('&')
    744     else:
    745       cookie_values = ("",)
    746     self.send_response(200)
    747     self.send_header('Content-type', 'text/html')
    748     for cookie_value in cookie_values:
    749       self.send_header('Set-Cookie', '%s' % cookie_value)
    750     self.end_headers()
    751     for cookie_value in cookie_values:
    752       self.wfile.write('%s' % cookie_value)
    753     return True
    754 
    755   def AuthBasicHandler(self):
    756     """This handler tests 'Basic' authentication.  It just sends a page with
    757     title 'user/pass' if you succeed."""
    758 
    759     if not self._ShouldHandleRequest("/auth-basic"):
    760       return False
    761 
    762     username = userpass = password = b64str = ""
    763 
    764     set_cookie_if_challenged = self.path.find('?set-cookie-if-challenged') > 0
    765 
    766     auth = self.headers.getheader('authorization')
    767     try:
    768       if not auth:
    769         raise Exception('no auth')
    770       b64str = re.findall(r'Basic (\S+)', auth)[0]
    771       userpass = base64.b64decode(b64str)
    772       username, password = re.findall(r'([^:]+):(\S+)', userpass)[0]
    773       if password != 'secret':
    774         raise Exception('wrong password')
    775     except Exception, e:
    776       # Authentication failed.
    777       self.send_response(401)
    778       self.send_header('WWW-Authenticate', 'Basic realm="testrealm"')
    779       self.send_header('Content-type', 'text/html')
    780       if set_cookie_if_challenged:
    781         self.send_header('Set-Cookie', 'got_challenged=true')
    782       self.end_headers()
    783       self.wfile.write('<html><head>')
    784       self.wfile.write('<title>Denied: %s</title>' % e)
    785       self.wfile.write('</head><body>')
    786       self.wfile.write('auth=%s<p>' % auth)
    787       self.wfile.write('b64str=%s<p>' % b64str)
    788       self.wfile.write('username: %s<p>' % username)
    789       self.wfile.write('userpass: %s<p>' % userpass)
    790       self.wfile.write('password: %s<p>' % password)
    791       self.wfile.write('You sent:<br>%s<p>' % self.headers)
    792       self.wfile.write('</body></html>')
    793       return True
    794 
    795     # Authentication successful.  (Return a cachable response to allow for
    796     # testing cached pages that require authentication.)
    797     if_none_match = self.headers.getheader('if-none-match')
    798     if if_none_match == "abc":
    799       self.send_response(304)
    800       self.end_headers()
    801     else:
    802       self.send_response(200)
    803       self.send_header('Content-type', 'text/html')
    804       self.send_header('Cache-control', 'max-age=60000')
    805       self.send_header('Etag', 'abc')
    806       self.end_headers()
    807       self.wfile.write('<html><head>')
    808       self.wfile.write('<title>%s/%s</title>' % (username, password))
    809       self.wfile.write('</head><body>')
    810       self.wfile.write('auth=%s<p>' % auth)
    811       self.wfile.write('You sent:<br>%s<p>' % self.headers)
    812       self.wfile.write('</body></html>')
    813 
    814     return True
    815 
    816   def AuthDigestHandler(self):
    817     """This handler tests 'Digest' authentication.  It just sends a page with
    818     title 'user/pass' if you succeed."""
    819 
    820     if not self._ShouldHandleRequest("/auth-digest"):
    821       return False
    822 
    823     # Periodically generate a new nonce.  Technically we should incorporate
    824     # the request URL into this, but we don't care for testing.
    825     nonce_life = 10
    826     stale = False
    827     if (not self.server.nonce or
    828         (time.time() - self.server.nonce_time > nonce_life)):
    829       if self.server.nonce:
    830         stale = True
    831       self.server.nonce_time = time.time()
    832       self.server.nonce = \
    833           _new_md5(time.ctime(self.server.nonce_time) +
    834                    'privatekey').hexdigest()
    835 
    836     nonce = self.server.nonce
    837     opaque = _new_md5('opaque').hexdigest()
    838     password = 'secret'
    839     realm = 'testrealm'
    840 
    841     auth = self.headers.getheader('authorization')
    842     pairs = {}
    843     try:
    844       if not auth:
    845         raise Exception('no auth')
    846       if not auth.startswith('Digest'):
    847         raise Exception('not digest')
    848       # Pull out all the name="value" pairs as a dictionary.
    849       pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth))
    850 
    851       # Make sure it's all valid.
    852       if pairs['nonce'] != nonce:
    853         raise Exception('wrong nonce')
    854       if pairs['opaque'] != opaque:
    855         raise Exception('wrong opaque')
    856 
    857       # Check the 'response' value and make sure it matches our magic hash.
    858       # See http://www.ietf.org/rfc/rfc2617.txt
    859       hash_a1 = _new_md5(
    860           ':'.join([pairs['username'], realm, password])).hexdigest()
    861       hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest()
    862       if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs:
    863         response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'],
    864             pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest()
    865       else:
    866         response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest()
    867 
    868       if pairs['response'] != response:
    869         raise Exception('wrong password')
    870     except Exception, e:
    871       # Authentication failed.
    872       self.send_response(401)
    873       hdr = ('Digest '
    874              'realm="%s", '
    875              'domain="/", '
    876              'qop="auth", '
    877              'algorithm=MD5, '
    878              'nonce="%s", '
    879              'opaque="%s"') % (realm, nonce, opaque)
    880       if stale:
    881         hdr += ', stale="TRUE"'
    882       self.send_header('WWW-Authenticate', hdr)
    883       self.send_header('Content-type', 'text/html')
    884       self.end_headers()
    885       self.wfile.write('<html><head>')
    886       self.wfile.write('<title>Denied: %s</title>' % e)
    887       self.wfile.write('</head><body>')
    888       self.wfile.write('auth=%s<p>' % auth)
    889       self.wfile.write('pairs=%s<p>' % pairs)
    890       self.wfile.write('You sent:<br>%s<p>' % self.headers)
    891       self.wfile.write('We are replying:<br>%s<p>' % hdr)
    892       self.wfile.write('</body></html>')
    893       return True
    894 
    895     # Authentication successful.
    896     self.send_response(200)
    897     self.send_header('Content-type', 'text/html')
    898     self.end_headers()
    899     self.wfile.write('<html><head>')
    900     self.wfile.write('<title>%s/%s</title>' % (pairs['username'], password))
    901     self.wfile.write('</head><body>')
    902     self.wfile.write('auth=%s<p>' % auth)
    903     self.wfile.write('pairs=%s<p>' % pairs)
    904     self.wfile.write('</body></html>')
    905 
    906     return True
    907 
    908   def SlowServerHandler(self):
    909     """Wait for the user suggested time before responding. The syntax is
    910     /slow?0.5 to wait for half a second."""
    911     if not self._ShouldHandleRequest("/slow"):
    912       return False
    913     query_char = self.path.find('?')
    914     wait_sec = 1.0
    915     if query_char >= 0:
    916       try:
    917         wait_sec = int(self.path[query_char + 1:])
    918       except ValueError:
    919         pass
    920     time.sleep(wait_sec)
    921     self.send_response(200)
    922     self.send_header('Content-type', 'text/plain')
    923     self.end_headers()
    924     self.wfile.write("waited %d seconds" % wait_sec)
    925     return True
    926 
    927   def ContentTypeHandler(self):
    928     """Returns a string of html with the given content type.  E.g.,
    929     /contenttype?text/css returns an html file with the Content-Type
    930     header set to text/css."""
    931     if not self._ShouldHandleRequest("/contenttype"):
    932       return False
    933     query_char = self.path.find('?')
    934     content_type = self.path[query_char + 1:].strip()
    935     if not content_type:
    936       content_type = 'text/html'
    937     self.send_response(200)
    938     self.send_header('Content-Type', content_type)
    939     self.end_headers()
    940     self.wfile.write("<html>\n<body>\n<p>HTML text</p>\n</body>\n</html>\n");
    941     return True
    942 
    943   def ServerRedirectHandler(self):
    944     """Sends a server redirect to the given URL. The syntax is
    945     '/server-redirect?http://foo.bar/asdf' to redirect to
    946     'http://foo.bar/asdf'"""
    947 
    948     test_name = "/server-redirect"
    949     if not self._ShouldHandleRequest(test_name):
    950       return False
    951 
    952     query_char = self.path.find('?')
    953     if query_char < 0 or len(self.path) <= query_char + 1:
    954       self.sendRedirectHelp(test_name)
    955       return True
    956     dest = self.path[query_char + 1:]
    957 
    958     self.send_response(301)  # moved permanently
    959     self.send_header('Location', dest)
    960     self.send_header('Content-type', 'text/html')
    961     self.end_headers()
    962     self.wfile.write('<html><head>')
    963     self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
    964 
    965     return True
    966 
    967   def ClientRedirectHandler(self):
    968     """Sends a client redirect to the given URL. The syntax is
    969     '/client-redirect?http://foo.bar/asdf' to redirect to
    970     'http://foo.bar/asdf'"""
    971 
    972     test_name = "/client-redirect"
    973     if not self._ShouldHandleRequest(test_name):
    974       return False
    975 
    976     query_char = self.path.find('?');
    977     if query_char < 0 or len(self.path) <= query_char + 1:
    978       self.sendRedirectHelp(test_name)
    979       return True
    980     dest = self.path[query_char + 1:]
    981 
    982     self.send_response(200)
    983     self.send_header('Content-type', 'text/html')
    984     self.end_headers()
    985     self.wfile.write('<html><head>')
    986     self.wfile.write('<meta http-equiv="refresh" content="0;url=%s">' % dest)
    987     self.wfile.write('</head><body>Redirecting to %s</body></html>' % dest)
    988 
    989     return True
    990 
    991   def DefaultResponseHandler(self):
    992     """This is the catch-all response handler for requests that aren't handled
    993     by one of the special handlers above.
    994     Note that we specify the content-length as without it the https connection
    995     is not closed properly (and the browser keeps expecting data)."""
    996 
    997     contents = "Default response given for path: " + self.path
    998     self.send_response(200)
    999     self.send_header('Content-type', 'text/html')
   1000     self.send_header("Content-Length", len(contents))
   1001     self.end_headers()
   1002     self.wfile.write(contents)
   1003     return True
   1004 
   1005   def RedirectConnectHandler(self):
   1006     """Sends a redirect to the CONNECT request for www.redirect.com. This
   1007     response is not specified by the RFC, so the browser should not follow
   1008     the redirect."""
   1009 
   1010     if (self.path.find("www.redirect.com") < 0):
   1011       return False
   1012 
   1013     dest = "http://www.destination.com/foo.js"
   1014 
   1015     self.send_response(302)  # moved temporarily
   1016     self.send_header('Location', dest)
   1017     self.send_header('Connection', 'close')
   1018     self.end_headers()
   1019     return True
   1020 
   1021   def ServerAuthConnectHandler(self):
   1022     """Sends a 401 to the CONNECT request for www.server-auth.com. This
   1023     response doesn't make sense because the proxy server cannot request
   1024     server authentication."""
   1025 
   1026     if (self.path.find("www.server-auth.com") < 0):
   1027       return False
   1028 
   1029     challenge = 'Basic realm="WallyWorld"'
   1030 
   1031     self.send_response(401)  # unauthorized
   1032     self.send_header('WWW-Authenticate', challenge)
   1033     self.send_header('Connection', 'close')
   1034     self.end_headers()
   1035     return True
   1036 
   1037   def DefaultConnectResponseHandler(self):
   1038     """This is the catch-all response handler for CONNECT requests that aren't
   1039     handled by one of the special handlers above.  Real Web servers respond
   1040     with 400 to CONNECT requests."""
   1041 
   1042     contents = "Your client has issued a malformed or illegal request."
   1043     self.send_response(400)  # bad request
   1044     self.send_header('Content-type', 'text/html')
   1045     self.send_header("Content-Length", len(contents))
   1046     self.end_headers()
   1047     self.wfile.write(contents)
   1048     return True
   1049 
   1050   def do_CONNECT(self):
   1051     for handler in self._connect_handlers:
   1052       if handler():
   1053         return
   1054 
   1055   def do_GET(self):
   1056     for handler in self._get_handlers:
   1057       if handler():
   1058         return
   1059 
   1060   def do_POST(self):
   1061     for handler in self._post_handlers:
   1062       if handler():
   1063         return
   1064 
   1065   def do_PUT(self):
   1066     for handler in self._put_handlers:
   1067       if handler():
   1068         return
   1069 
   1070   # called by the redirect handling function when there is no parameter
   1071   def sendRedirectHelp(self, redirect_name):
   1072     self.send_response(200)
   1073     self.send_header('Content-type', 'text/html')
   1074     self.end_headers()
   1075     self.wfile.write('<html><body><h1>Error: no redirect destination</h1>')
   1076     self.wfile.write('Use <pre>%s?http://dest...</pre>' % redirect_name)
   1077     self.wfile.write('</body></html>')
   1078 
   1079 def MakeDumpDir(data_dir):
   1080   """Create directory named 'dump' where uploaded data via HTTP POST/PUT
   1081   requests will be stored. If the directory already exists all files and
   1082   subdirectories will be deleted."""
   1083   dump_dir = os.path.join(data_dir, 'dump');
   1084   if os.path.isdir(dump_dir):
   1085     shutil.rmtree(dump_dir)
   1086   os.mkdir(dump_dir)
   1087 
   1088 def MakeDataDir():
   1089   if options.data_dir:
   1090     if not os.path.isdir(options.data_dir):
   1091       print 'specified data dir not found: ' + options.data_dir + ' exiting...'
   1092       return None
   1093     my_data_dir = options.data_dir
   1094   else:
   1095     # Create the default path to our data dir, relative to the exe dir.
   1096     my_data_dir = os.path.dirname(sys.argv[0])
   1097     my_data_dir = os.path.join(my_data_dir, "..", "..", "..", "..",
   1098                                    "test", "data")
   1099 
   1100     #TODO(ibrar): Must use Find* funtion defined in google\tools
   1101     #i.e my_data_dir = FindUpward(my_data_dir, "test", "data")
   1102 
   1103   return my_data_dir
   1104 
   1105 def main(options, args):
   1106   # redirect output to a log file so it doesn't spam the unit test output
   1107   logfile = open('testserver.log', 'w')
   1108   sys.stderr = sys.stdout = logfile
   1109 
   1110   port = options.port
   1111 
   1112   if options.server_type == SERVER_HTTP:
   1113     if options.cert:
   1114       # let's make sure the cert file exists.
   1115       if not os.path.isfile(options.cert):
   1116         print 'specified cert file not found: ' + options.cert + ' exiting...'
   1117         return
   1118       if options.forking:
   1119         server_class = ForkingHTTPSServer
   1120       else:
   1121         server_class = HTTPSServer
   1122       server = server_class(('127.0.0.1', port), TestPageHandler, options.cert)
   1123       print 'HTTPS server started on port %d...' % port
   1124     else:
   1125       if options.forking:
   1126         server_class = ForkingHTTPServer
   1127       else:
   1128         server_class = StoppableHTTPServer
   1129       server = server_class(('127.0.0.1', port), TestPageHandler)
   1130       print 'HTTP server started on port %d...' % port
   1131 
   1132     server.data_dir = MakeDataDir()
   1133     server.file_root_url = options.file_root_url
   1134     MakeDumpDir(server.data_dir)
   1135 
   1136   # means FTP Server
   1137   else:
   1138     my_data_dir = MakeDataDir()
   1139 
   1140     def line_logger(msg):
   1141       if (msg.find("kill") >= 0):
   1142         server.stop = True
   1143         print 'shutting down server'
   1144         sys.exit(0)
   1145 
   1146     # Instantiate a dummy authorizer for managing 'virtual' users
   1147     authorizer = pyftpdlib.ftpserver.DummyAuthorizer()
   1148 
   1149     # Define a new user having full r/w permissions and a read-only
   1150     # anonymous user
   1151     authorizer.add_user('chrome', 'chrome', my_data_dir, perm='elradfmw')
   1152 
   1153     authorizer.add_anonymous(my_data_dir)
   1154 
   1155     # Instantiate FTP handler class
   1156     ftp_handler = pyftpdlib.ftpserver.FTPHandler
   1157     ftp_handler.authorizer = authorizer
   1158     pyftpdlib.ftpserver.logline = line_logger
   1159 
   1160     # Define a customized banner (string returned when client connects)
   1161     ftp_handler.banner = ("pyftpdlib %s based ftpd ready." %
   1162                           pyftpdlib.ftpserver.__ver__)
   1163 
   1164     # Instantiate FTP server class and listen to 127.0.0.1:port
   1165     address = ('127.0.0.1', port)
   1166     server = pyftpdlib.ftpserver.FTPServer(address, ftp_handler)
   1167     print 'FTP server started on port %d...' % port
   1168 
   1169   try:
   1170     server.serve_forever()
   1171   except KeyboardInterrupt:
   1172     print 'shutting down server'
   1173     server.stop = True
   1174 
   1175 if __name__ == '__main__':
   1176   option_parser = optparse.OptionParser()
   1177   option_parser.add_option("-f", '--ftp', action='store_const',
   1178                            const=SERVER_FTP, default=SERVER_HTTP,
   1179                            dest='server_type',
   1180                            help='FTP or HTTP server: default is HTTP.')
   1181   option_parser.add_option('--forking', action='store_true', default=False,
   1182                            dest='forking',
   1183                            help='Serve each request in a separate process.')
   1184   option_parser.add_option('', '--port', default='8888', type='int',
   1185                            help='Port used by the server.')
   1186   option_parser.add_option('', '--data-dir', dest='data_dir',
   1187                            help='Directory from which to read the files.')
   1188   option_parser.add_option('', '--https', dest='cert',
   1189                            help='Specify that https should be used, specify '
   1190                            'the path to the cert containing the private key '
   1191                            'the server should use.')
   1192   option_parser.add_option('', '--file-root-url', default='/files/',
   1193                            help='Specify a root URL for files served.')
   1194   option_parser.add_option('', '--never-die', default=False,
   1195                            action="store_true",
   1196                            help='Prevent the server from dying when visiting '
   1197                            'a /kill URL. Useful for manually running some '
   1198                            'tests.')
   1199   options, args = option_parser.parse_args()
   1200 
   1201   sys.exit(main(options, args))
   1202