2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """This is a python sync server used for testing Chrome Sync.
8 By default, it listens on an ephemeral port and xmpp_port and sends the port
9 numbers back to the originating process over a pipe. The originating process can
10 specify an explicit port and xmpp_port if necessary.
24 import testserver_base
28 class SyncHTTPServer(testserver_base.ClientRestrictingServerMixIn,
29 testserver_base.BrokenPipeHandlerMixIn,
30 testserver_base.StoppableHTTPServer):
31 """An HTTP server that handles sync commands."""
33 def __init__(self, server_address, xmpp_port, request_handler_class):
34 testserver_base.StoppableHTTPServer.__init__(self,
36 request_handler_class)
37 self._sync_handler = chromiumsync.TestServer()
38 self._xmpp_socket_map = {}
39 self._xmpp_server = xmppserver.XmppServer(
40 self._xmpp_socket_map, ('localhost', xmpp_port))
41 self.xmpp_port = self._xmpp_server.getsockname()[1]
42 self.authenticated = True
44 def GetXmppServer(self):
45 return self._xmpp_server
47 def HandleCommand(self, query, raw_request):
48 return self._sync_handler.HandleCommand(query, raw_request)
50 def HandleRequestNoBlock(self):
51 """Handles a single request.
53 Copied from SocketServer._handle_request_noblock().
57 request, client_address = self.get_request()
60 if self.verify_request(request, client_address):
62 self.process_request(request, client_address)
64 self.handle_error(request, client_address)
65 self.close_request(request)
67 def SetAuthenticated(self, auth_valid):
68 self.authenticated = auth_valid
70 def GetAuthenticated(self):
71 return self.authenticated
73 def serve_forever(self):
74 """This is a merge of asyncore.loop() and SocketServer.serve_forever().
77 def HandleXmppSocket(fd, socket_map, handler):
78 """Runs the handler for the xmpp connection for fd.
80 Adapted from asyncore.read() et al.
83 xmpp_connection = socket_map.get(fd)
84 # This could happen if a previous handler call caused fd to get
85 # removed from socket_map.
86 if xmpp_connection is None:
89 handler(xmpp_connection)
90 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit):
93 xmpp_connection.handle_error()
96 read_fds = [ self.fileno() ]
100 for fd, xmpp_connection in self._xmpp_socket_map.items():
101 is_r = xmpp_connection.readable()
102 is_w = xmpp_connection.writable()
108 exceptional_fds.append(fd)
111 read_fds, write_fds, exceptional_fds = (
112 select.select(read_fds, write_fds, exceptional_fds))
113 except select.error, err:
114 if err.args[0] != errno.EINTR:
120 if fd == self.fileno():
121 self.HandleRequestNoBlock()
123 HandleXmppSocket(fd, self._xmpp_socket_map,
124 asyncore.dispatcher.handle_read_event)
127 HandleXmppSocket(fd, self._xmpp_socket_map,
128 asyncore.dispatcher.handle_write_event)
130 for fd in exceptional_fds:
131 HandleXmppSocket(fd, self._xmpp_socket_map,
132 asyncore.dispatcher.handle_expt_event)
135 class SyncPageHandler(testserver_base.BasePageHandler):
136 """Handler for the main HTTP sync server."""
138 def __init__(self, request, client_address, sync_http_server):
139 get_handlers = [self.ChromiumSyncTimeHandler,
140 self.ChromiumSyncMigrationOpHandler,
141 self.ChromiumSyncCredHandler,
142 self.ChromiumSyncXmppCredHandler,
143 self.ChromiumSyncDisableNotificationsOpHandler,
144 self.ChromiumSyncEnableNotificationsOpHandler,
145 self.ChromiumSyncSendNotificationOpHandler,
146 self.ChromiumSyncBirthdayErrorOpHandler,
147 self.ChromiumSyncTransientErrorOpHandler,
148 self.ChromiumSyncErrorOpHandler,
149 self.ChromiumSyncSyncTabFaviconsOpHandler,
150 self.ChromiumSyncCreateSyncedBookmarksOpHandler,
151 self.ChromiumSyncEnableKeystoreEncryptionOpHandler,
152 self.ChromiumSyncRotateKeystoreKeysOpHandler,
153 self.ChromiumSyncEnableManagedUserAcknowledgementHandler,
154 self.ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler]
156 post_handlers = [self.ChromiumSyncCommandHandler,
157 self.ChromiumSyncTimeHandler]
158 testserver_base.BasePageHandler.__init__(self, request, client_address,
159 sync_http_server, [], get_handlers,
160 [], post_handlers, [])
163 def ChromiumSyncTimeHandler(self):
164 """Handle Chromium sync .../time requests.
166 The syncer sometimes checks server reachability by examining /time.
169 test_name = "/chromiumsync/time"
170 if not self._ShouldHandleRequest(test_name):
173 # Chrome hates it if we send a response before reading the request.
174 if self.headers.getheader('content-length'):
175 length = int(self.headers.getheader('content-length'))
176 _raw_request = self.rfile.read(length)
178 self.send_response(200)
179 self.send_header('Content-Type', 'text/plain')
181 self.wfile.write('0123456789')
184 def ChromiumSyncCommandHandler(self):
185 """Handle a chromiumsync command arriving via http.
187 This covers all sync protocol commands: authentication, getupdates, and
191 test_name = "/chromiumsync/command"
192 if not self._ShouldHandleRequest(test_name):
195 length = int(self.headers.getheader('content-length'))
196 raw_request = self.rfile.read(length)
199 if not self.server.GetAuthenticated():
201 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
202 self.server.server_address[0])
204 http_response, raw_reply = self.server.HandleCommand(
205 self.path, raw_request)
207 ### Now send the response to the client. ###
208 self.send_response(http_response)
209 if http_response == 401:
210 self.send_header('www-Authenticate', challenge)
212 self.wfile.write(raw_reply)
215 def ChromiumSyncMigrationOpHandler(self):
216 test_name = "/chromiumsync/migrate"
217 if not self._ShouldHandleRequest(test_name):
220 http_response, raw_reply = self.server._sync_handler.HandleMigrate(
222 self.send_response(http_response)
223 self.send_header('Content-Type', 'text/html')
224 self.send_header('Content-Length', len(raw_reply))
226 self.wfile.write(raw_reply)
229 def ChromiumSyncCredHandler(self):
230 test_name = "/chromiumsync/cred"
231 if not self._ShouldHandleRequest(test_name):
234 query = urlparse.urlparse(self.path)[4]
235 cred_valid = urlparse.parse_qs(query)['valid']
236 if cred_valid[0] == 'True':
237 self.server.SetAuthenticated(True)
239 self.server.SetAuthenticated(False)
241 self.server.SetAuthenticated(False)
244 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
245 self.send_response(http_response)
246 self.send_header('Content-Type', 'text/html')
247 self.send_header('Content-Length', len(raw_reply))
249 self.wfile.write(raw_reply)
252 def ChromiumSyncXmppCredHandler(self):
253 test_name = "/chromiumsync/xmppcred"
254 if not self._ShouldHandleRequest(test_name):
256 xmpp_server = self.server.GetXmppServer()
258 query = urlparse.urlparse(self.path)[4]
259 cred_valid = urlparse.parse_qs(query)['valid']
260 if cred_valid[0] == 'True':
261 xmpp_server.SetAuthenticated(True)
263 xmpp_server.SetAuthenticated(False)
265 xmpp_server.SetAuthenticated(False)
268 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
269 self.send_response(http_response)
270 self.send_header('Content-Type', 'text/html')
271 self.send_header('Content-Length', len(raw_reply))
273 self.wfile.write(raw_reply)
276 def ChromiumSyncDisableNotificationsOpHandler(self):
277 test_name = "/chromiumsync/disablenotifications"
278 if not self._ShouldHandleRequest(test_name):
280 self.server.GetXmppServer().DisableNotifications()
282 raw_reply = ('<html><title>Notifications disabled</title>'
283 '<H1>Notifications disabled</H1></html>')
284 self.send_response(result)
285 self.send_header('Content-Type', 'text/html')
286 self.send_header('Content-Length', len(raw_reply))
288 self.wfile.write(raw_reply)
291 def ChromiumSyncEnableNotificationsOpHandler(self):
292 test_name = "/chromiumsync/enablenotifications"
293 if not self._ShouldHandleRequest(test_name):
295 self.server.GetXmppServer().EnableNotifications()
297 raw_reply = ('<html><title>Notifications enabled</title>'
298 '<H1>Notifications enabled</H1></html>')
299 self.send_response(result)
300 self.send_header('Content-Type', 'text/html')
301 self.send_header('Content-Length', len(raw_reply))
303 self.wfile.write(raw_reply)
306 def ChromiumSyncSendNotificationOpHandler(self):
307 test_name = "/chromiumsync/sendnotification"
308 if not self._ShouldHandleRequest(test_name):
310 query = urlparse.urlparse(self.path)[4]
311 query_params = urlparse.parse_qs(query)
314 if 'channel' in query_params:
315 channel = query_params['channel'][0]
316 if 'data' in query_params:
317 data = query_params['data'][0]
318 self.server.GetXmppServer().SendNotification(channel, data)
320 raw_reply = ('<html><title>Notification sent</title>'
321 '<H1>Notification sent with channel "%s" '
322 'and data "%s"</H1></html>'
324 self.send_response(result)
325 self.send_header('Content-Type', 'text/html')
326 self.send_header('Content-Length', len(raw_reply))
328 self.wfile.write(raw_reply)
331 def ChromiumSyncBirthdayErrorOpHandler(self):
332 test_name = "/chromiumsync/birthdayerror"
333 if not self._ShouldHandleRequest(test_name):
335 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
336 self.send_response(result)
337 self.send_header('Content-Type', 'text/html')
338 self.send_header('Content-Length', len(raw_reply))
340 self.wfile.write(raw_reply)
343 def ChromiumSyncTransientErrorOpHandler(self):
344 test_name = "/chromiumsync/transienterror"
345 if not self._ShouldHandleRequest(test_name):
347 result, raw_reply = self.server._sync_handler.HandleSetTransientError()
348 self.send_response(result)
349 self.send_header('Content-Type', 'text/html')
350 self.send_header('Content-Length', len(raw_reply))
352 self.wfile.write(raw_reply)
355 def ChromiumSyncErrorOpHandler(self):
356 test_name = "/chromiumsync/error"
357 if not self._ShouldHandleRequest(test_name):
359 result, raw_reply = self.server._sync_handler.HandleSetInducedError(
361 self.send_response(result)
362 self.send_header('Content-Type', 'text/html')
363 self.send_header('Content-Length', len(raw_reply))
365 self.wfile.write(raw_reply)
368 def ChromiumSyncSyncTabFaviconsOpHandler(self):
369 test_name = "/chromiumsync/synctabfavicons"
370 if not self._ShouldHandleRequest(test_name):
372 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
373 self.send_response(result)
374 self.send_header('Content-Type', 'text/html')
375 self.send_header('Content-Length', len(raw_reply))
377 self.wfile.write(raw_reply)
380 def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
381 test_name = "/chromiumsync/createsyncedbookmarks"
382 if not self._ShouldHandleRequest(test_name):
384 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
385 self.send_response(result)
386 self.send_header('Content-Type', 'text/html')
387 self.send_header('Content-Length', len(raw_reply))
389 self.wfile.write(raw_reply)
392 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
393 test_name = "/chromiumsync/enablekeystoreencryption"
394 if not self._ShouldHandleRequest(test_name):
396 result, raw_reply = (
397 self.server._sync_handler.HandleEnableKeystoreEncryption())
398 self.send_response(result)
399 self.send_header('Content-Type', 'text/html')
400 self.send_header('Content-Length', len(raw_reply))
402 self.wfile.write(raw_reply)
405 def ChromiumSyncRotateKeystoreKeysOpHandler(self):
406 test_name = "/chromiumsync/rotatekeystorekeys"
407 if not self._ShouldHandleRequest(test_name):
409 result, raw_reply = (
410 self.server._sync_handler.HandleRotateKeystoreKeys())
411 self.send_response(result)
412 self.send_header('Content-Type', 'text/html')
413 self.send_header('Content-Length', len(raw_reply))
415 self.wfile.write(raw_reply)
418 def ChromiumSyncEnableManagedUserAcknowledgementHandler(self):
419 test_name = "/chromiumsync/enablemanageduseracknowledgement"
420 if not self._ShouldHandleRequest(test_name):
422 result, raw_reply = (
423 self.server._sync_handler.HandleEnableManagedUserAcknowledgement())
424 self.send_response(result)
425 self.send_header('Content-Type', 'text/html')
426 self.send_header('Content-Length', len(raw_reply))
428 self.wfile.write(raw_reply)
431 def ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler(self):
432 test_name = "/chromiumsync/enableprecommitgetupdateavoidance"
433 if not self._ShouldHandleRequest(test_name):
435 result, raw_reply = (
436 self.server._sync_handler.HandleEnablePreCommitGetUpdateAvoidance())
437 self.send_response(result)
438 self.send_header('Content-Type', 'text/html')
439 self.send_header('Content-Length', len(raw_reply))
441 self.wfile.write(raw_reply)
444 class SyncServerRunner(testserver_base.TestServerRunner):
445 """TestServerRunner for the net test servers."""
448 super(SyncServerRunner, self).__init__()
450 def create_server(self, server_data):
451 port = self.options.port
452 host = self.options.host
453 xmpp_port = self.options.xmpp_port
454 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
455 print 'Sync HTTP server started on port %d...' % server.server_port
456 print 'Sync XMPP server started on port %d...' % server.xmpp_port
457 server_data['port'] = server.server_port
458 server_data['xmpp_port'] = server.xmpp_port
461 def run_server(self):
462 testserver_base.TestServerRunner.run_server(self)
464 def add_options(self):
465 testserver_base.TestServerRunner.add_options(self)
466 self.option_parser.add_option('--xmpp-port', default='0', type='int',
467 help='Port used by the XMPP server. If '
468 'unspecified, the XMPP server will listen on '
469 'an ephemeral port.')
470 # Override the default logfile name used in testserver.py.
471 self.option_parser.set_defaults(log_file='sync_testserver.log')
473 if __name__ == '__main__':
474 sys.exit(SyncServerRunner().main())