#!/usr/bin/env python # Copyright 2013 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 python sync server used for testing Chrome Sync. By default, it listens on an ephemeral port and xmpp_port and sends the port numbers back to the originating process over a pipe. The originating process can specify an explicit port and xmpp_port if necessary. """ import asyncore import BaseHTTPServer import errno import gzip import os import select import StringIO import socket import sys import urlparse import chromiumsync import echo_message import testserver_base import xmppserver class SyncHTTPServer(testserver_base.ClientRestrictingServerMixIn, testserver_base.BrokenPipeHandlerMixIn, testserver_base.StoppableHTTPServer): """An HTTP server that handles sync commands.""" def __init__(self, server_address, xmpp_port, request_handler_class): testserver_base.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', xmpp_port)) 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 Exception: 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 handle_request(self): """Adaptation of asyncore.loop""" 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() 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: return for fd in read_fds: if fd == self.fileno(): self.HandleRequestNoBlock() return 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 SyncPageHandler(testserver_base.BasePageHandler): """Handler for the main HTTP sync server.""" def __init__(self, request, client_address, sync_http_server): get_handlers = [self.ChromiumSyncTimeHandler, self.ChromiumSyncMigrationOpHandler, self.ChromiumSyncCredHandler, self.ChromiumSyncXmppCredHandler, self.ChromiumSyncDisableNotificationsOpHandler, self.ChromiumSyncEnableNotificationsOpHandler, self.ChromiumSyncSendNotificationOpHandler, self.ChromiumSyncBirthdayErrorOpHandler, self.ChromiumSyncTransientErrorOpHandler, self.ChromiumSyncErrorOpHandler, self.ChromiumSyncSyncTabFaviconsOpHandler, self.ChromiumSyncCreateSyncedBookmarksOpHandler, self.ChromiumSyncEnableKeystoreEncryptionOpHandler, self.ChromiumSyncRotateKeystoreKeysOpHandler, self.ChromiumSyncEnableManagedUserAcknowledgementHandler, self.ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler, self.GaiaOAuth2TokenHandler, self.GaiaSetOAuth2TokenResponseHandler, self.CustomizeClientCommandHandler] post_handlers = [self.ChromiumSyncCommandHandler, self.ChromiumSyncTimeHandler, self.GaiaOAuth2TokenHandler, self.GaiaSetOAuth2TokenResponseHandler] testserver_base.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) if self.headers.getheader('Content-Encoding'): encode = self.headers.getheader('Content-Encoding') if encode == "gzip": raw_request = gzip.GzipFile( fileobj=StringIO.StringIO(raw_request)).read() http_response = 200 raw_reply = None if not self.server.GetAuthenticated(): http_response = 401 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % ( self.server.server_address[0]) 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 Exception: 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 ChromiumSyncXmppCredHandler(self): test_name = "/chromiumsync/xmppcred" if not self._ShouldHandleRequest(test_name): return False xmpp_server = self.server.GetXmppServer() try: query = urlparse.urlparse(self.path)[4] cred_valid = urlparse.parse_qs(query)['valid'] if cred_valid[0] == 'True': xmpp_server.SetAuthenticated(True) else: xmpp_server.SetAuthenticated(False) except: xmpp_server.SetAuthenticated(False) http_response = 200 raw_reply = 'XMPP Authenticated: %s ' % xmpp_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 = ('
{}
.".format(command_string)
except ValueError:
response_code = 400
reply = "sessions_commit_delay_seconds was not an int"
else:
response_code = 400
reply = "sessions_commit_delay_seconds is required"
self.send_response(response_code)
self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', len(reply))
self.end_headers()
self.wfile.write(reply)
return True
class SyncServerRunner(testserver_base.TestServerRunner):
"""TestServerRunner for the net test servers."""
def __init__(self):
super(SyncServerRunner, self).__init__()
def create_server(self, server_data):
port = self.options.port
host = self.options.host
xmpp_port = self.options.xmpp_port
server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
print ('Sync HTTP server started at %s:%d/chromiumsync...' %
(host, server.server_port))
print ('Fake OAuth2 Token server started at %s:%d/o/oauth2/token...' %
(host, server.server_port))
print ('Sync XMPP server started at %s:%d...' %
(host, server.xmpp_port))
server_data['port'] = server.server_port
server_data['xmpp_port'] = server.xmpp_port
return server
def run_server(self):
testserver_base.TestServerRunner.run_server(self)
def add_options(self):
testserver_base.TestServerRunner.add_options(self)
self.option_parser.add_option('--xmpp-port', default='0', type='int',
help='Port used by the XMPP server. If '
'unspecified, the XMPP server will listen on '
'an ephemeral port.')
# Override the default logfile name used in testserver.py.
self.option_parser.set_defaults(log_file='sync_testserver.log')
if __name__ == '__main__':
sys.exit(SyncServerRunner().main())