| Index: sync/tools/testserver/sync_testserver.py
|
| diff --git a/sync/tools/testserver/sync_testserver.py b/sync/tools/testserver/sync_testserver.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..692577689439aaf5f0ed3a87f5bb26fc0a53a442
|
| --- /dev/null
|
| +++ b/sync/tools/testserver/sync_testserver.py
|
| @@ -0,0 +1,447 @@
|
| +#!/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 os
|
| +import select
|
| +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 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 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]
|
| +
|
| + post_handlers = [self.ChromiumSyncCommandHandler,
|
| + self.ChromiumSyncTimeHandler]
|
| + 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)
|
| + 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 = ('<html><title>Notifications disabled</title>'
|
| + '<H1>Notifications disabled</H1></html>')
|
| + self.send_response(result)
|
| + 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 ChromiumSyncEnableNotificationsOpHandler(self):
|
| + test_name = "/chromiumsync/enablenotifications"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + self.server.GetXmppServer().EnableNotifications()
|
| + result = 200
|
| + raw_reply = ('<html><title>Notifications enabled</title>'
|
| + '<H1>Notifications enabled</H1></html>')
|
| + self.send_response(result)
|
| + 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 ChromiumSyncSendNotificationOpHandler(self):
|
| + test_name = "/chromiumsync/sendnotification"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + query = urlparse.urlparse(self.path)[4]
|
| + query_params = urlparse.parse_qs(query)
|
| + channel = ''
|
| + data = ''
|
| + if 'channel' in query_params:
|
| + channel = query_params['channel'][0]
|
| + if 'data' in query_params:
|
| + data = query_params['data'][0]
|
| + self.server.GetXmppServer().SendNotification(channel, data)
|
| + result = 200
|
| + raw_reply = ('<html><title>Notification sent</title>'
|
| + '<H1>Notification sent with channel "%s" '
|
| + 'and data "%s"</H1></html>'
|
| + % (channel, data))
|
| + self.send_response(result)
|
| + 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 ChromiumSyncBirthdayErrorOpHandler(self):
|
| + test_name = "/chromiumsync/birthdayerror"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
|
| + self.send_response(result)
|
| + 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 ChromiumSyncTransientErrorOpHandler(self):
|
| + test_name = "/chromiumsync/transienterror"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = self.server._sync_handler.HandleSetTransientError()
|
| + self.send_response(result)
|
| + 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 ChromiumSyncErrorOpHandler(self):
|
| + test_name = "/chromiumsync/error"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = self.server._sync_handler.HandleSetInducedError(
|
| + self.path)
|
| + self.send_response(result)
|
| + 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 ChromiumSyncSyncTabFaviconsOpHandler(self):
|
| + test_name = "/chromiumsync/synctabfavicons"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
|
| + self.send_response(result)
|
| + 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 ChromiumSyncCreateSyncedBookmarksOpHandler(self):
|
| + test_name = "/chromiumsync/createsyncedbookmarks"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
|
| + self.send_response(result)
|
| + 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 ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
|
| + test_name = "/chromiumsync/enablekeystoreencryption"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = (
|
| + self.server._sync_handler.HandleEnableKeystoreEncryption())
|
| + self.send_response(result)
|
| + 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 ChromiumSyncRotateKeystoreKeysOpHandler(self):
|
| + test_name = "/chromiumsync/rotatekeystorekeys"
|
| + if not self._ShouldHandleRequest(test_name):
|
| + return False
|
| + result, raw_reply = (
|
| + self.server._sync_handler.HandleRotateKeystoreKeys())
|
| + self.send_response(result)
|
| + 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
|
| +
|
| +
|
| +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 on port %d...' % server.server_port
|
| + print 'Sync XMPP server started on port %d...' % 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())
|
|
|