#!/usr/bin/env python # Copyright (c) 2011 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """This is a simple HTTP/FTP/SYNC/TCP/UDP/ server used for testing Chrome. It supports several test URLs, as specified by the handlers in TestPageHandler. By default, it listens on an ephemeral port and sends the port number back to the originating process over a pipe. The originating process can specify an explicit port if necessary. It can use https if you specify the flag --https=CERT where CERT is the path to a pem file containing the certificate and private key that should be used. """ import asyncore import base64 import BaseHTTPServer import cgi import errno import optparse import os import random import re import select import SocketServer import socket import sys import struct import time import urllib import urlparse import warnings import zlib # Ignore deprecation warnings, they make our output more cluttered. warnings.filterwarnings("ignore", category=DeprecationWarning) import echo_message import pyftpdlib.ftpserver import tlslite import tlslite.api try: import hashlib _new_md5 = hashlib.md5 except ImportError: import md5 _new_md5 = md5.new try: import json except ImportError: import simplejson as json if sys.platform == 'win32': import msvcrt SERVER_HTTP = 0 SERVER_FTP = 1 SERVER_SYNC = 2 SERVER_TCP_ECHO = 3 SERVER_UDP_ECHO = 4 # Using debug() seems to cause hangs on XP: see http://crbug.com/64515 . debug_output = sys.stderr def debug(str): debug_output.write(str + "\n") debug_output.flush() class StoppableHTTPServer(BaseHTTPServer.HTTPServer): """This is a specialization of of BaseHTTPServer to allow it to be exited cleanly (by setting its "stop" member to True).""" def serve_forever(self): self.stop = False self.nonce_time = None while not self.stop: self.handle_request() self.socket.close() class HTTPSServer(tlslite.api.TLSSocketServerMixIn, StoppableHTTPServer): """This is a specialization of StoppableHTTPerver that add https support.""" def __init__(self, server_address, request_hander_class, cert_path, ssl_client_auth, ssl_client_cas, ssl_bulk_ciphers): s = open(cert_path).read() x509 = tlslite.api.X509() x509.parse(s) self.cert_chain = tlslite.api.X509CertChain([x509]) s = open(cert_path).read() self.private_key = tlslite.api.parsePEMKey(s, private=True) self.ssl_client_auth = ssl_client_auth self.ssl_client_cas = [] for ca_file in ssl_client_cas: s = open(ca_file).read() x509 = tlslite.api.X509() x509.parse(s) self.ssl_client_cas.append(x509.subject) self.ssl_handshake_settings = tlslite.api.HandshakeSettings() if ssl_bulk_ciphers is not None: self.ssl_handshake_settings.cipherNames = ssl_bulk_ciphers self.session_cache = tlslite.api.SessionCache() StoppableHTTPServer.__init__(self, server_address, request_hander_class) def handshake(self, tlsConnection): """Creates the SSL connection.""" try: tlsConnection.handshakeServer(certChain=self.cert_chain, privateKey=self.private_key, sessionCache=self.session_cache, reqCert=self.ssl_client_auth, settings=self.ssl_handshake_settings, reqCAs=self.ssl_client_cas) tlsConnection.ignoreAbruptClose = True return True except tlslite.api.TLSAbruptCloseError: # Ignore abrupt close. return True except tlslite.api.TLSError, error: print "Handshake failure:", str(error) return False class SyncHTTPServer(StoppableHTTPServer): """An HTTP server that handles sync commands.""" def __init__(self, server_address, request_handler_class): # We import here to avoid pulling in chromiumsync's dependencies # unless strictly necessary. import chromiumsync import xmppserver StoppableHTTPServer.__init__(self, server_address, request_handler_class) self._sync_handler = chromiumsync.TestServer() self._xmpp_socket_map = {} self._xmpp_server = xmppserver.XmppServer( self._xmpp_socket_map, ('localhost', 0)) self.xmpp_port = self._xmpp_server.getsockname()[1] self.authenticated = True def GetXmppServer(self): return self._xmpp_server def HandleCommand(self, query, raw_request): return self._sync_handler.HandleCommand(query, raw_request) def HandleRequestNoBlock(self): """Handles a single request. Copied from SocketServer._handle_request_noblock(). """ try: request, client_address = self.get_request() except socket.error: return if self.verify_request(request, client_address): try: self.process_request(request, client_address) except: self.handle_error(request, client_address) self.close_request(request) def SetAuthenticated(self, auth_valid): self.authenticated = auth_valid def GetAuthenticated(self): return self.authenticated def serve_forever(self): """This is a merge of asyncore.loop() and SocketServer.serve_forever(). """ def HandleXmppSocket(fd, socket_map, handler): """Runs the handler for the xmpp connection for fd. Adapted from asyncore.read() et al. """ xmpp_connection = socket_map.get(fd) # This could happen if a previous handler call caused fd to get # removed from socket_map. if xmpp_connection is None: return try: handler(xmpp_connection) except (asyncore.ExitNow, KeyboardInterrupt, SystemExit): raise except: xmpp_connection.handle_error() while True: read_fds = [ self.fileno() ] write_fds = [] exceptional_fds = [] for fd, xmpp_connection in self._xmpp_socket_map.items(): is_r = xmpp_connection.readable() is_w = xmpp_connection.writable() if is_r: read_fds.append(fd) if is_w: write_fds.append(fd) if is_r or is_w: exceptional_fds.append(fd) try: read_fds, write_fds, exceptional_fds = ( select.select(read_fds, write_fds, exceptional_fds)) except select.error, err: if err.args[0] != errno.EINTR: raise else: continue for fd in read_fds: if fd == self.fileno(): self.HandleRequestNoBlock() continue HandleXmppSocket(fd, self._xmpp_socket_map, asyncore.dispatcher.handle_read_event) for fd in write_fds: HandleXmppSocket(fd, self._xmpp_socket_map, asyncore.dispatcher.handle_write_event) for fd in exceptional_fds: HandleXmppSocket(fd, self._xmpp_socket_map, asyncore.dispatcher.handle_expt_event) class TCPEchoServer(SocketServer.TCPServer): """A TCP echo server that echoes back what it has received.""" def server_bind(self): """Override server_bind to store the server name.""" SocketServer.TCPServer.server_bind(self) host, port = self.socket.getsockname()[:2] self.server_name = socket.getfqdn(host) self.server_port = port def serve_forever(self): self.stop = False self.nonce_time = None while not self.stop: self.handle_request() self.socket.close() class UDPEchoServer(SocketServer.UDPServer): """A UDP echo server that echoes back what it has received.""" def server_bind(self): """Override server_bind to store the server name.""" SocketServer.UDPServer.server_bind(self) host, port = self.socket.getsockname()[:2] self.server_name = socket.getfqdn(host) self.server_port = port def serve_forever(self): self.stop = False self.nonce_time = None while not self.stop: self.handle_request() self.socket.close() class BasePageHandler(BaseHTTPServer.BaseHTTPRequestHandler): def __init__(self, request, client_address, socket_server, connect_handlers, get_handlers, head_handlers, post_handlers, put_handlers): self._connect_handlers = connect_handlers self._get_handlers = get_handlers self._head_handlers = head_handlers self._post_handlers = post_handlers self._put_handlers = put_handlers BaseHTTPServer.BaseHTTPRequestHandler.__init__( self, request, client_address, socket_server) def log_request(self, *args, **kwargs): # Disable request logging to declutter test log output. pass def _ShouldHandleRequest(self, handler_name): """Determines if the path can be handled by the handler. We consider a handler valid if the path begins with the handler name. It can optionally be followed by "?*", "/*". """ pattern = re.compile('%s($|\?|/).*' % handler_name) return pattern.match(self.path) def do_CONNECT(self): for handler in self._connect_handlers: if handler(): return def do_GET(self): for handler in self._get_handlers: if handler(): return def do_HEAD(self): for handler in self._head_handlers: if handler(): return def do_POST(self): for handler in self._post_handlers: if handler(): return def do_PUT(self): for handler in self._put_handlers: if handler(): return class TestPageHandler(BasePageHandler): def __init__(self, request, client_address, socket_server): connect_handlers = [ self.RedirectConnectHandler, self.ServerAuthConnectHandler, self.DefaultConnectResponseHandler] get_handlers = [ self.NoCacheMaxAgeTimeHandler, self.NoCacheTimeHandler, self.CacheTimeHandler, self.CacheExpiresHandler, self.CacheProxyRevalidateHandler, self.CachePrivateHandler, self.CachePublicHandler, self.CacheSMaxAgeHandler, self.CacheMustRevalidateHandler, self.CacheMustRevalidateMaxAgeHandler, self.CacheNoStoreHandler, self.CacheNoStoreMaxAgeHandler, self.CacheNoTransformHandler, self.DownloadHandler, self.DownloadFinishHandler, self.EchoHeader, self.EchoHeaderCache, self.EchoAllHandler, self.ZipFileHandler, self.FileHandler, self.SetCookieHandler, self.SetHeaderHandler, self.AuthBasicHandler, self.AuthDigestHandler, self.SlowServerHandler, self.ChunkedServerHandler, self.ContentTypeHandler, self.NoContentHandler, self.ServerRedirectHandler, self.ClientRedirectHandler, self.MultipartHandler, self.MultipartSlowHandler, self.DefaultResponseHandler] post_handlers = [ self.EchoTitleHandler, self.EchoHandler, self.DeviceManagementHandler] + get_handlers put_handlers = [ self.EchoTitleHandler, self.EchoHandler] + get_handlers head_handlers = [ self.FileHandler, self.DefaultResponseHandler] self._mime_types = { 'crx' : 'application/x-chrome-extension', 'exe' : 'application/octet-stream', 'gif': 'image/gif', 'jpeg' : 'image/jpeg', 'jpg' : 'image/jpeg', 'pdf' : 'application/pdf', 'xml' : 'text/xml' } self._default_mime_type = 'text/html' BasePageHandler.__init__(self, request, client_address, socket_server, connect_handlers, get_handlers, head_handlers, post_handlers, put_handlers) def GetMIMETypeFromName(self, file_name): """Returns the mime type for the specified file_name. So far it only looks at the file extension.""" (shortname, extension) = os.path.splitext(file_name.split("?")[0]) if len(extension) == 0: # no extension. return self._default_mime_type # extension starts with a dot, so we need to remove it return self._mime_types.get(extension[1:], self._default_mime_type) def NoCacheMaxAgeTimeHandler(self): """This request handler yields a page with the title set to the current system time, and no caching requested.""" if not self._ShouldHandleRequest("/nocachetime/maxage"): return False self.send_response(200) self.send_header('Cache-Control', 'max-age=0') self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write('
') if self.command == 'POST' or self.command == 'PUT': qs = self.ReadRequestBody() params = cgi.parse_qs(qs, keep_blank_values=1) for param in params: self.wfile.write('%s=%s\n' % (param, params[param][0])) self.wfile.write('') self.wfile.write('
%s' % self.headers) self.wfile.write('') return True def DownloadHandler(self): """This handler sends a downloadable file with or without reporting the size (6K).""" if self.path.startswith("/download-unknown-size"): send_length = False elif self.path.startswith("/download-known-size"): send_length = True else: return False # # The test which uses this functionality is attempting to send # small chunks of data to the client. Use a fairly large buffer # so that we'll fill chrome's IO buffer enough to force it to # actually write the data. # See also the comments in the client-side of this test in # download_uitest.cc # size_chunk1 = 35*1024 size_chunk2 = 10*1024 self.send_response(200) self.send_header('Content-Type', 'application/octet-stream') self.send_header('Cache-Control', 'max-age=0') if send_length: self.send_header('Content-Length', size_chunk1 + size_chunk2) self.end_headers() # First chunk of data: self.wfile.write("*" * size_chunk1) self.wfile.flush() # handle requests until one of them clears this flag. self.server.waitForDownload = True while self.server.waitForDownload: self.server.handle_request() # Second chunk of data: self.wfile.write("*" * size_chunk2) return True def DownloadFinishHandler(self): """This handler just tells the server to finish the current download.""" if not self._ShouldHandleRequest("/download-finish"): return False self.server.waitForDownload = False self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Cache-Control', 'max-age=0') self.end_headers() return True def _ReplaceFileData(self, data, query_parameters): """Replaces matching substrings in a file. If the 'replace_text' URL query parameter is present, it is expected to be of the form old_text:new_text, which indicates that any old_text strings in the file are replaced with new_text. Multiple 'replace_text' parameters may be specified. If the parameters are not present, |data| is returned. """ query_dict = cgi.parse_qs(query_parameters) replace_text_values = query_dict.get('replace_text', []) for replace_text_value in replace_text_values: replace_text_args = replace_text_value.split(':') if len(replace_text_args) != 2: raise ValueError( 'replace_text must be of form old_text:new_text. Actual value: %s' % replace_text_value) old_text_b64, new_text_b64 = replace_text_args old_text = base64.urlsafe_b64decode(old_text_b64) new_text = base64.urlsafe_b64decode(new_text_b64) data = data.replace(old_text, new_text) return data def ZipFileHandler(self): """This handler sends the contents of the requested file in compressed form. Can pass in a parameter that specifies that the content length be C - the compressed size (OK), U - the uncompressed size (Non-standard, but handled), S - less than compressed (OK because we keep going), M - larger than compressed but less than uncompressed (an error), L - larger than uncompressed (an error) Example: compressedfiles/Picture_1.doc?C """ prefix = "/compressedfiles/" if not self.path.startswith(prefix): return False # Consume a request body if present. if self.command == 'POST' or self.command == 'PUT' : self.ReadRequestBody() _, _, url_path, _, query, _ = urlparse.urlparse(self.path) if not query in ('C', 'U', 'S', 'M', 'L'): return False sub_path = url_path[len(prefix):] entries = sub_path.split('/') file_path = os.path.join(self.server.data_dir, *entries) if os.path.isdir(file_path): file_path = os.path.join(file_path, 'index.html') if not os.path.isfile(file_path): print "File not found " + sub_path + " full path:" + file_path self.send_error(404) return True f = open(file_path, "rb") data = f.read() uncompressed_len = len(data) f.close() # Compress the data. data = zlib.compress(data) compressed_len = len(data) content_length = compressed_len if query == 'U': content_length = uncompressed_len elif query == 'S': content_length = compressed_len / 2 elif query == 'M': content_length = (compressed_len + uncompressed_len) / 2 elif query == 'L': content_length = compressed_len + uncompressed_len self.send_response(200) self.send_header('Content-Type', 'application/msword') self.send_header('Content-encoding', 'deflate') self.send_header('Connection', 'close') self.send_header('Content-Length', content_length) self.send_header('ETag', '\'' + file_path + '\'') self.end_headers() self.wfile.write(data) return True def FileHandler(self): """This handler sends the contents of the requested file. Wow, it's like a real webserver!""" prefix = self.server.file_root_url if not self.path.startswith(prefix): return False # Consume a request body if present. if self.command == 'POST' or self.command == 'PUT' : self.ReadRequestBody() _, _, url_path, _, query, _ = urlparse.urlparse(self.path) sub_path = url_path[len(prefix):] entries = sub_path.split('/') file_path = os.path.join(self.server.data_dir, *entries) if os.path.isdir(file_path): file_path = os.path.join(file_path, 'index.html') if not os.path.isfile(file_path): print "File not found " + sub_path + " full path:" + file_path self.send_error(404) return True f = open(file_path, "rb") data = f.read() f.close() data = self._ReplaceFileData(data, query) # If file.mock-http-headers exists, it contains the headers we # should send. Read them in and parse them. headers_path = file_path + '.mock-http-headers' if os.path.isfile(headers_path): f = open(headers_path, "r") # "HTTP/1.1 200 OK" response = f.readline() status_code = re.findall('HTTP/\d+.\d+ (\d+)', response)[0] self.send_response(int(status_code)) for line in f: header_values = re.findall('(\S+):\s*(.*)', line) if len(header_values) > 0: # "name: value" name, value = header_values[0] self.send_header(name, value) f.close() else: # Could be more generic once we support mime-type sniffing, but for # now we need to set it explicitly. range = self.headers.get('Range') if range and range.startswith('bytes='): # Note this doesn't handle all valid byte range values (i.e. open ended # ones), just enough for what we needed so far. range = range[6:].split('-') start = int(range[0]) end = int(range[1]) self.send_response(206) content_range = 'bytes ' + str(start) + '-' + str(end) + '/' + \ str(len(data)) self.send_header('Content-Range', content_range) data = data[start: end + 1] else: self.send_response(200) self.send_header('Content-Type', self.GetMIMETypeFromName(file_path)) self.send_header('Accept-Ranges', 'bytes') self.send_header('Content-Length', len(data)) self.send_header('ETag', '\'' + file_path + '\'') self.end_headers() if (self.command != 'HEAD'): self.wfile.write(data) return True def SetCookieHandler(self): """This handler just sets a cookie, for testing cookie handling.""" if not self._ShouldHandleRequest("/set-cookie"): return False query_char = self.path.find('?') if query_char != -1: cookie_values = self.path[query_char + 1:].split('&') else: cookie_values = ("",) self.send_response(200) self.send_header('Content-Type', 'text/html') for cookie_value in cookie_values: self.send_header('Set-Cookie', '%s' % cookie_value) self.end_headers() for cookie_value in cookie_values: self.wfile.write('%s' % cookie_value) return True def SetHeaderHandler(self): """This handler sets a response header. Parameters are in the key%3A%20value&key2%3A%20value2 format.""" if not self._ShouldHandleRequest("/set-header"): return False query_char = self.path.find('?') if query_char != -1: headers_values = self.path[query_char + 1:].split('&') else: headers_values = ("",) self.send_response(200) self.send_header('Content-Type', 'text/html') for header_value in headers_values: header_value = urllib.unquote(header_value) (key, value) = header_value.split(': ', 1) self.send_header(key, value) self.end_headers() for header_value in headers_values: self.wfile.write('%s' % header_value) return True def AuthBasicHandler(self): """This handler tests 'Basic' authentication. It just sends a page with title 'user/pass' if you succeed.""" if not self._ShouldHandleRequest("/auth-basic"): return False username = userpass = password = b64str = "" expected_password = 'secret' realm = 'testrealm' set_cookie_if_challenged = False _, _, url_path, _, query, _ = urlparse.urlparse(self.path) query_params = cgi.parse_qs(query, True) if 'set-cookie-if-challenged' in query_params: set_cookie_if_challenged = True if 'password' in query_params: expected_password = query_params['password'][0] if 'realm' in query_params: realm = query_params['realm'][0] auth = self.headers.getheader('authorization') try: if not auth: raise Exception('no auth') b64str = re.findall(r'Basic (\S+)', auth)[0] userpass = base64.b64decode(b64str) username, password = re.findall(r'([^:]+):(\S+)', userpass)[0] if password != expected_password: raise Exception('wrong password') except Exception, e: # Authentication failed. self.send_response(401) self.send_header('WWW-Authenticate', 'Basic realm="%s"' % realm) self.send_header('Content-Type', 'text/html') if set_cookie_if_challenged: self.send_header('Set-Cookie', 'got_challenged=true') self.end_headers() self.wfile.write('') self.wfile.write('
' % auth) self.wfile.write('b64str=%s
' % b64str) self.wfile.write('username: %s
' % username) self.wfile.write('userpass: %s
' % userpass) self.wfile.write('password: %s
' % password)
self.wfile.write('You sent:
%s
' % self.headers) self.wfile.write('') return True # Authentication successful. (Return a cachable response to allow for # testing cached pages that require authentication.) old_protocol_version = self.protocol_version self.protocol_version = "HTTP/1.1" if_none_match = self.headers.getheader('if-none-match') if if_none_match == "abc": self.send_response(304) self.end_headers() elif url_path.endswith(".gif"): # Using chrome/test/data/google/logo.gif as the test image test_image_path = ['google', 'logo.gif'] gif_path = os.path.join(self.server.data_dir, *test_image_path) if not os.path.isfile(gif_path): self.send_error(404) self.protocol_version = old_protocol_version return True f = open(gif_path, "rb") data = f.read() f.close() self.send_response(200) self.send_header('Content-Type', 'image/gif') self.send_header('Cache-control', 'max-age=60000') self.send_header('Etag', 'abc') self.end_headers() self.wfile.write(data) else: self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Cache-control', 'max-age=60000') self.send_header('Etag', 'abc') self.end_headers() self.wfile.write('
') self.wfile.write('' % auth)
self.wfile.write('You sent:
%s
' % self.headers) self.wfile.write('') self.protocol_version = old_protocol_version return True def GetNonce(self, force_reset=False): """Returns a nonce that's stable per request path for the server's lifetime. This is a fake implementation. A real implementation would only use a given nonce a single time (hence the name n-once). However, for the purposes of unittesting, we don't care about the security of the nonce. Args: force_reset: Iff set, the nonce will be changed. Useful for testing the "stale" response. """ if force_reset or not self.server.nonce_time: self.server.nonce_time = time.time() return _new_md5('privatekey%s%d' % (self.path, self.server.nonce_time)).hexdigest() def AuthDigestHandler(self): """This handler tests 'Digest' authentication. It just sends a page with title 'user/pass' if you succeed. A stale response is sent iff "stale" is present in the request path. """ if not self._ShouldHandleRequest("/auth-digest"): return False stale = 'stale' in self.path nonce = self.GetNonce(force_reset=stale) opaque = _new_md5('opaque').hexdigest() password = 'secret' realm = 'testrealm' auth = self.headers.getheader('authorization') pairs = {} try: if not auth: raise Exception('no auth') if not auth.startswith('Digest'): raise Exception('not digest') # Pull out all the name="value" pairs as a dictionary. pairs = dict(re.findall(r'(\b[^ ,=]+)="?([^",]+)"?', auth)) # Make sure it's all valid. if pairs['nonce'] != nonce: raise Exception('wrong nonce') if pairs['opaque'] != opaque: raise Exception('wrong opaque') # Check the 'response' value and make sure it matches our magic hash. # See http://www.ietf.org/rfc/rfc2617.txt hash_a1 = _new_md5( ':'.join([pairs['username'], realm, password])).hexdigest() hash_a2 = _new_md5(':'.join([self.command, pairs['uri']])).hexdigest() if 'qop' in pairs and 'nc' in pairs and 'cnonce' in pairs: response = _new_md5(':'.join([hash_a1, nonce, pairs['nc'], pairs['cnonce'], pairs['qop'], hash_a2])).hexdigest() else: response = _new_md5(':'.join([hash_a1, nonce, hash_a2])).hexdigest() if pairs['response'] != response: raise Exception('wrong password') except Exception, e: # Authentication failed. self.send_response(401) hdr = ('Digest ' 'realm="%s", ' 'domain="/", ' 'qop="auth", ' 'algorithm=MD5, ' 'nonce="%s", ' 'opaque="%s"') % (realm, nonce, opaque) if stale: hdr += ', stale="TRUE"' self.send_header('WWW-Authenticate', hdr) self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write('
') self.wfile.write('' % auth) self.wfile.write('pairs=%s
' % pairs)
self.wfile.write('You sent:
%s
' % self.headers)
self.wfile.write('We are replying:
%s
' % hdr) self.wfile.write('') return True # Authentication successful. self.send_response(200) self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write('
') self.wfile.write('' % auth) self.wfile.write('pairs=%s
' % pairs) self.wfile.write('') return True def SlowServerHandler(self): """Wait for the user suggested time before responding. The syntax is /slow?0.5 to wait for half a second.""" if not self._ShouldHandleRequest("/slow"): return False query_char = self.path.find('?') wait_sec = 1.0 if query_char >= 0: try: wait_sec = int(self.path[query_char + 1:]) except ValueError: pass time.sleep(wait_sec) self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write("waited %d seconds" % wait_sec) return True def ChunkedServerHandler(self): """Send chunked response. Allows to specify chunks parameters: - waitBeforeHeaders - ms to wait before sending headers - waitBetweenChunks - ms to wait between chunks - chunkSize - size of each chunk in bytes - chunksNumber - number of chunks Example: /chunked?waitBeforeHeaders=1000&chunkSize=5&chunksNumber=5 waits one second, then sends headers and five chunks five bytes each.""" if not self._ShouldHandleRequest("/chunked"): return False query_char = self.path.find('?') chunkedSettings = {'waitBeforeHeaders' : 0, 'waitBetweenChunks' : 0, 'chunkSize' : 5, 'chunksNumber' : 5} if query_char >= 0: params = self.path[query_char + 1:].split('&') for param in params: keyValue = param.split('=') if len(keyValue) == 2: try: chunkedSettings[keyValue[0]] = int(keyValue[1]) except ValueError: pass time.sleep(0.001 * chunkedSettings['waitBeforeHeaders']); self.protocol_version = 'HTTP/1.1' # Needed for chunked encoding self.send_response(200) self.send_header('Content-Type', 'text/plain') self.send_header('Connection', 'close') self.send_header('Transfer-Encoding', 'chunked') self.end_headers() # Chunked encoding: sending all chunks, then final zero-length chunk and # then final CRLF. for i in range(0, chunkedSettings['chunksNumber']): if i > 0: time.sleep(0.001 * chunkedSettings['waitBetweenChunks']) self.sendChunkHelp('*' * chunkedSettings['chunkSize']) self.wfile.flush(); # Keep in mind that we start flushing only after 1kb. self.sendChunkHelp('') return True def ContentTypeHandler(self): """Returns a string of html with the given content type. E.g., /contenttype?text/css returns an html file with the Content-Type header set to text/css.""" if not self._ShouldHandleRequest("/contenttype"): return False query_char = self.path.find('?') content_type = self.path[query_char + 1:].strip() if not content_type: content_type = 'text/html' self.send_response(200) self.send_header('Content-Type', content_type) self.end_headers() self.wfile.write("\n
\nHTML text
\n\n\n"); return True def NoContentHandler(self): """Returns a 204 No Content response.""" if not self._ShouldHandleRequest("/nocontent"): return False self.send_response(204) self.end_headers() return True def ServerRedirectHandler(self): """Sends a server redirect to the given URL. The syntax is '/server-redirect?http://foo.bar/asdf' to redirect to 'http://foo.bar/asdf'""" test_name = "/server-redirect" if not self._ShouldHandleRequest(test_name): return False query_char = self.path.find('?') if query_char < 0 or len(self.path) <= query_char + 1: self.sendRedirectHelp(test_name) return True dest = self.path[query_char + 1:] self.send_response(301) # moved permanently self.send_header('Location', dest) self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write('') self.wfile.write('Redirecting to %s' % dest) return True def ClientRedirectHandler(self): """Sends a client redirect to the given URL. The syntax is '/client-redirect?http://foo.bar/asdf' to redirect to 'http://foo.bar/asdf'""" test_name = "/client-redirect" if not self._ShouldHandleRequest(test_name): return False query_char = self.path.find('?'); if query_char < 0 or len(self.path) <= query_char + 1: self.sendRedirectHelp(test_name) return True dest = self.path[query_char + 1:] self.send_response(200) self.send_header('Content-Type', 'text/html') self.end_headers() self.wfile.write('') self.wfile.write('' % dest) self.wfile.write('Redirecting to %s' % dest) return True def MultipartHandler(self): """Send a multipart response (10 text/html pages).""" test_name = '/multipart' if not self._ShouldHandleRequest(test_name): return False num_frames = 10 bound = '12345' self.send_response(200) self.send_header('Content-Type', 'multipart/x-mixed-replace;boundary=' + bound) self.end_headers() for i in xrange(num_frames): self.wfile.write('--' + bound + '\r\n') self.wfile.write('Content-Type: text/html\r\n\r\n') self.wfile.write('%s?http://dest...' % redirect_name) self.wfile.write('') # called by chunked handling function def sendChunkHelp(self, chunk): # Each chunk consists of: chunk size (hex), CRLF, chunk body, CRLF self.wfile.write('%X\r\n' % len(chunk)) self.wfile.write(chunk) self.wfile.write('\r\n') class SyncPageHandler(BasePageHandler): """Handler for the main HTTP sync server.""" def __init__(self, request, client_address, sync_http_server): get_handlers = [self.ChromiumSyncMigrationOpHandler, self.ChromiumSyncTimeHandler, self.ChromiumSyncDisableNotificationsOpHandler, self.ChromiumSyncEnableNotificationsOpHandler, self.ChromiumSyncSendNotificationOpHandler, self.ChromiumSyncBirthdayErrorOpHandler, self.ChromiumSyncTransientErrorOpHandler, self.ChromiumSyncSyncTabsOpHandler, self.ChromiumSyncErrorOpHandler, self.ChromiumSyncCredHandler] post_handlers = [self.ChromiumSyncCommandHandler, self.ChromiumSyncTimeHandler] BasePageHandler.__init__(self, request, client_address, sync_http_server, [], get_handlers, [], post_handlers, []) def ChromiumSyncTimeHandler(self): """Handle Chromium sync .../time requests. The syncer sometimes checks server reachability by examining /time. """ test_name = "/chromiumsync/time" if not self._ShouldHandleRequest(test_name): return False # Chrome hates it if we send a response before reading the request. if self.headers.getheader('content-length'): length = int(self.headers.getheader('content-length')) raw_request = self.rfile.read(length) self.send_response(200) self.send_header('Content-Type', 'text/plain') self.end_headers() self.wfile.write('0123456789') return True def ChromiumSyncCommandHandler(self): """Handle a chromiumsync command arriving via http. This covers all sync protocol commands: authentication, getupdates, and commit. """ test_name = "/chromiumsync/command" if not self._ShouldHandleRequest(test_name): return False length = int(self.headers.getheader('content-length')) raw_request = self.rfile.read(length) http_response = 200 raw_reply = None if not self.server.GetAuthenticated(): http_response = 401 challenge = 'GoogleLogin realm="http://127.0.0.1", service="chromiumsync"' else: http_response, raw_reply = self.server.HandleCommand( self.path, raw_request) ### Now send the response to the client. ### self.send_response(http_response) if http_response == 401: self.send_header('www-Authenticate', challenge) self.end_headers() self.wfile.write(raw_reply) return True def ChromiumSyncMigrationOpHandler(self): test_name = "/chromiumsync/migrate" if not self._ShouldHandleRequest(test_name): return False http_response, raw_reply = self.server._sync_handler.HandleMigrate( self.path) self.send_response(http_response) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(raw_reply)) self.end_headers() self.wfile.write(raw_reply) return True def ChromiumSyncCredHandler(self): test_name = "/chromiumsync/cred" if not self._ShouldHandleRequest(test_name): return False try: query = urlparse.urlparse(self.path)[4] cred_valid = urlparse.parse_qs(query)['valid'] if cred_valid[0] == 'True': self.server.SetAuthenticated(True) else: self.server.SetAuthenticated(False) except: self.server.SetAuthenticated(False) http_response = 200 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated() self.send_response(http_response) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(raw_reply)) self.end_headers() self.wfile.write(raw_reply) return True def ChromiumSyncDisableNotificationsOpHandler(self): test_name = "/chromiumsync/disablenotifications" if not self._ShouldHandleRequest(test_name): return False self.server.GetXmppServer().DisableNotifications() result = 200 raw_reply = ('