OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env python |
| 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. |
| 5 |
| 6 """This is a python sync server used for testing Chrome Sync. |
| 7 |
| 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. |
| 11 """ |
| 12 |
| 13 import asyncore |
| 14 import BaseHTTPServer |
| 15 import errno |
| 16 import os |
| 17 import select |
| 18 import socket |
| 19 import sys |
| 20 import urlparse |
| 21 |
| 22 import chromiumsync |
| 23 import echo_message |
| 24 import testserver_base |
| 25 import xmppserver |
| 26 |
| 27 |
| 28 class SyncHTTPServer(testserver_base.ClientRestrictingServerMixIn, |
| 29 testserver_base.BrokenPipeHandlerMixIn, |
| 30 testserver_base.StoppableHTTPServer): |
| 31 """An HTTP server that handles sync commands.""" |
| 32 |
| 33 def __init__(self, server_address, xmpp_port, request_handler_class): |
| 34 testserver_base.StoppableHTTPServer.__init__(self, |
| 35 server_address, |
| 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 |
| 43 |
| 44 def GetXmppServer(self): |
| 45 return self._xmpp_server |
| 46 |
| 47 def HandleCommand(self, query, raw_request): |
| 48 return self._sync_handler.HandleCommand(query, raw_request) |
| 49 |
| 50 def HandleRequestNoBlock(self): |
| 51 """Handles a single request. |
| 52 |
| 53 Copied from SocketServer._handle_request_noblock(). |
| 54 """ |
| 55 |
| 56 try: |
| 57 request, client_address = self.get_request() |
| 58 except socket.error: |
| 59 return |
| 60 if self.verify_request(request, client_address): |
| 61 try: |
| 62 self.process_request(request, client_address) |
| 63 except Exception: |
| 64 self.handle_error(request, client_address) |
| 65 self.close_request(request) |
| 66 |
| 67 def SetAuthenticated(self, auth_valid): |
| 68 self.authenticated = auth_valid |
| 69 |
| 70 def GetAuthenticated(self): |
| 71 return self.authenticated |
| 72 |
| 73 def serve_forever(self): |
| 74 """This is a merge of asyncore.loop() and SocketServer.serve_forever(). |
| 75 """ |
| 76 |
| 77 def HandleXmppSocket(fd, socket_map, handler): |
| 78 """Runs the handler for the xmpp connection for fd. |
| 79 |
| 80 Adapted from asyncore.read() et al. |
| 81 """ |
| 82 |
| 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: |
| 87 return |
| 88 try: |
| 89 handler(xmpp_connection) |
| 90 except (asyncore.ExitNow, KeyboardInterrupt, SystemExit): |
| 91 raise |
| 92 except: |
| 93 xmpp_connection.handle_error() |
| 94 |
| 95 while True: |
| 96 read_fds = [ self.fileno() ] |
| 97 write_fds = [] |
| 98 exceptional_fds = [] |
| 99 |
| 100 for fd, xmpp_connection in self._xmpp_socket_map.items(): |
| 101 is_r = xmpp_connection.readable() |
| 102 is_w = xmpp_connection.writable() |
| 103 if is_r: |
| 104 read_fds.append(fd) |
| 105 if is_w: |
| 106 write_fds.append(fd) |
| 107 if is_r or is_w: |
| 108 exceptional_fds.append(fd) |
| 109 |
| 110 try: |
| 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: |
| 115 raise |
| 116 else: |
| 117 continue |
| 118 |
| 119 for fd in read_fds: |
| 120 if fd == self.fileno(): |
| 121 self.HandleRequestNoBlock() |
| 122 continue |
| 123 HandleXmppSocket(fd, self._xmpp_socket_map, |
| 124 asyncore.dispatcher.handle_read_event) |
| 125 |
| 126 for fd in write_fds: |
| 127 HandleXmppSocket(fd, self._xmpp_socket_map, |
| 128 asyncore.dispatcher.handle_write_event) |
| 129 |
| 130 for fd in exceptional_fds: |
| 131 HandleXmppSocket(fd, self._xmpp_socket_map, |
| 132 asyncore.dispatcher.handle_expt_event) |
| 133 |
| 134 |
| 135 class SyncPageHandler(testserver_base.BasePageHandler): |
| 136 """Handler for the main HTTP sync server.""" |
| 137 |
| 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 |
| 154 post_handlers = [self.ChromiumSyncCommandHandler, |
| 155 self.ChromiumSyncTimeHandler] |
| 156 testserver_base.BasePageHandler.__init__(self, request, client_address, |
| 157 sync_http_server, [], get_handlers, |
| 158 [], post_handlers, []) |
| 159 |
| 160 |
| 161 def ChromiumSyncTimeHandler(self): |
| 162 """Handle Chromium sync .../time requests. |
| 163 |
| 164 The syncer sometimes checks server reachability by examining /time. |
| 165 """ |
| 166 |
| 167 test_name = "/chromiumsync/time" |
| 168 if not self._ShouldHandleRequest(test_name): |
| 169 return False |
| 170 |
| 171 # Chrome hates it if we send a response before reading the request. |
| 172 if self.headers.getheader('content-length'): |
| 173 length = int(self.headers.getheader('content-length')) |
| 174 _raw_request = self.rfile.read(length) |
| 175 |
| 176 self.send_response(200) |
| 177 self.send_header('Content-Type', 'text/plain') |
| 178 self.end_headers() |
| 179 self.wfile.write('0123456789') |
| 180 return True |
| 181 |
| 182 def ChromiumSyncCommandHandler(self): |
| 183 """Handle a chromiumsync command arriving via http. |
| 184 |
| 185 This covers all sync protocol commands: authentication, getupdates, and |
| 186 commit. |
| 187 """ |
| 188 |
| 189 test_name = "/chromiumsync/command" |
| 190 if not self._ShouldHandleRequest(test_name): |
| 191 return False |
| 192 |
| 193 length = int(self.headers.getheader('content-length')) |
| 194 raw_request = self.rfile.read(length) |
| 195 http_response = 200 |
| 196 raw_reply = None |
| 197 if not self.server.GetAuthenticated(): |
| 198 http_response = 401 |
| 199 challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % ( |
| 200 self.server.server_address[0]) |
| 201 else: |
| 202 http_response, raw_reply = self.server.HandleCommand( |
| 203 self.path, raw_request) |
| 204 |
| 205 ### Now send the response to the client. ### |
| 206 self.send_response(http_response) |
| 207 if http_response == 401: |
| 208 self.send_header('www-Authenticate', challenge) |
| 209 self.end_headers() |
| 210 self.wfile.write(raw_reply) |
| 211 return True |
| 212 |
| 213 def ChromiumSyncMigrationOpHandler(self): |
| 214 test_name = "/chromiumsync/migrate" |
| 215 if not self._ShouldHandleRequest(test_name): |
| 216 return False |
| 217 |
| 218 http_response, raw_reply = self.server._sync_handler.HandleMigrate( |
| 219 self.path) |
| 220 self.send_response(http_response) |
| 221 self.send_header('Content-Type', 'text/html') |
| 222 self.send_header('Content-Length', len(raw_reply)) |
| 223 self.end_headers() |
| 224 self.wfile.write(raw_reply) |
| 225 return True |
| 226 |
| 227 def ChromiumSyncCredHandler(self): |
| 228 test_name = "/chromiumsync/cred" |
| 229 if not self._ShouldHandleRequest(test_name): |
| 230 return False |
| 231 try: |
| 232 query = urlparse.urlparse(self.path)[4] |
| 233 cred_valid = urlparse.parse_qs(query)['valid'] |
| 234 if cred_valid[0] == 'True': |
| 235 self.server.SetAuthenticated(True) |
| 236 else: |
| 237 self.server.SetAuthenticated(False) |
| 238 except Exception: |
| 239 self.server.SetAuthenticated(False) |
| 240 |
| 241 http_response = 200 |
| 242 raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated() |
| 243 self.send_response(http_response) |
| 244 self.send_header('Content-Type', 'text/html') |
| 245 self.send_header('Content-Length', len(raw_reply)) |
| 246 self.end_headers() |
| 247 self.wfile.write(raw_reply) |
| 248 return True |
| 249 |
| 250 def ChromiumSyncXmppCredHandler(self): |
| 251 test_name = "/chromiumsync/xmppcred" |
| 252 if not self._ShouldHandleRequest(test_name): |
| 253 return False |
| 254 xmpp_server = self.server.GetXmppServer() |
| 255 try: |
| 256 query = urlparse.urlparse(self.path)[4] |
| 257 cred_valid = urlparse.parse_qs(query)['valid'] |
| 258 if cred_valid[0] == 'True': |
| 259 xmpp_server.SetAuthenticated(True) |
| 260 else: |
| 261 xmpp_server.SetAuthenticated(False) |
| 262 except: |
| 263 xmpp_server.SetAuthenticated(False) |
| 264 |
| 265 http_response = 200 |
| 266 raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated() |
| 267 self.send_response(http_response) |
| 268 self.send_header('Content-Type', 'text/html') |
| 269 self.send_header('Content-Length', len(raw_reply)) |
| 270 self.end_headers() |
| 271 self.wfile.write(raw_reply) |
| 272 return True |
| 273 |
| 274 def ChromiumSyncDisableNotificationsOpHandler(self): |
| 275 test_name = "/chromiumsync/disablenotifications" |
| 276 if not self._ShouldHandleRequest(test_name): |
| 277 return False |
| 278 self.server.GetXmppServer().DisableNotifications() |
| 279 result = 200 |
| 280 raw_reply = ('<html><title>Notifications disabled</title>' |
| 281 '<H1>Notifications disabled</H1></html>') |
| 282 self.send_response(result) |
| 283 self.send_header('Content-Type', 'text/html') |
| 284 self.send_header('Content-Length', len(raw_reply)) |
| 285 self.end_headers() |
| 286 self.wfile.write(raw_reply) |
| 287 return True |
| 288 |
| 289 def ChromiumSyncEnableNotificationsOpHandler(self): |
| 290 test_name = "/chromiumsync/enablenotifications" |
| 291 if not self._ShouldHandleRequest(test_name): |
| 292 return False |
| 293 self.server.GetXmppServer().EnableNotifications() |
| 294 result = 200 |
| 295 raw_reply = ('<html><title>Notifications enabled</title>' |
| 296 '<H1>Notifications enabled</H1></html>') |
| 297 self.send_response(result) |
| 298 self.send_header('Content-Type', 'text/html') |
| 299 self.send_header('Content-Length', len(raw_reply)) |
| 300 self.end_headers() |
| 301 self.wfile.write(raw_reply) |
| 302 return True |
| 303 |
| 304 def ChromiumSyncSendNotificationOpHandler(self): |
| 305 test_name = "/chromiumsync/sendnotification" |
| 306 if not self._ShouldHandleRequest(test_name): |
| 307 return False |
| 308 query = urlparse.urlparse(self.path)[4] |
| 309 query_params = urlparse.parse_qs(query) |
| 310 channel = '' |
| 311 data = '' |
| 312 if 'channel' in query_params: |
| 313 channel = query_params['channel'][0] |
| 314 if 'data' in query_params: |
| 315 data = query_params['data'][0] |
| 316 self.server.GetXmppServer().SendNotification(channel, data) |
| 317 result = 200 |
| 318 raw_reply = ('<html><title>Notification sent</title>' |
| 319 '<H1>Notification sent with channel "%s" ' |
| 320 'and data "%s"</H1></html>' |
| 321 % (channel, data)) |
| 322 self.send_response(result) |
| 323 self.send_header('Content-Type', 'text/html') |
| 324 self.send_header('Content-Length', len(raw_reply)) |
| 325 self.end_headers() |
| 326 self.wfile.write(raw_reply) |
| 327 return True |
| 328 |
| 329 def ChromiumSyncBirthdayErrorOpHandler(self): |
| 330 test_name = "/chromiumsync/birthdayerror" |
| 331 if not self._ShouldHandleRequest(test_name): |
| 332 return False |
| 333 result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError() |
| 334 self.send_response(result) |
| 335 self.send_header('Content-Type', 'text/html') |
| 336 self.send_header('Content-Length', len(raw_reply)) |
| 337 self.end_headers() |
| 338 self.wfile.write(raw_reply) |
| 339 return True |
| 340 |
| 341 def ChromiumSyncTransientErrorOpHandler(self): |
| 342 test_name = "/chromiumsync/transienterror" |
| 343 if not self._ShouldHandleRequest(test_name): |
| 344 return False |
| 345 result, raw_reply = self.server._sync_handler.HandleSetTransientError() |
| 346 self.send_response(result) |
| 347 self.send_header('Content-Type', 'text/html') |
| 348 self.send_header('Content-Length', len(raw_reply)) |
| 349 self.end_headers() |
| 350 self.wfile.write(raw_reply) |
| 351 return True |
| 352 |
| 353 def ChromiumSyncErrorOpHandler(self): |
| 354 test_name = "/chromiumsync/error" |
| 355 if not self._ShouldHandleRequest(test_name): |
| 356 return False |
| 357 result, raw_reply = self.server._sync_handler.HandleSetInducedError( |
| 358 self.path) |
| 359 self.send_response(result) |
| 360 self.send_header('Content-Type', 'text/html') |
| 361 self.send_header('Content-Length', len(raw_reply)) |
| 362 self.end_headers() |
| 363 self.wfile.write(raw_reply) |
| 364 return True |
| 365 |
| 366 def ChromiumSyncSyncTabFaviconsOpHandler(self): |
| 367 test_name = "/chromiumsync/synctabfavicons" |
| 368 if not self._ShouldHandleRequest(test_name): |
| 369 return False |
| 370 result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons() |
| 371 self.send_response(result) |
| 372 self.send_header('Content-Type', 'text/html') |
| 373 self.send_header('Content-Length', len(raw_reply)) |
| 374 self.end_headers() |
| 375 self.wfile.write(raw_reply) |
| 376 return True |
| 377 |
| 378 def ChromiumSyncCreateSyncedBookmarksOpHandler(self): |
| 379 test_name = "/chromiumsync/createsyncedbookmarks" |
| 380 if not self._ShouldHandleRequest(test_name): |
| 381 return False |
| 382 result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks() |
| 383 self.send_response(result) |
| 384 self.send_header('Content-Type', 'text/html') |
| 385 self.send_header('Content-Length', len(raw_reply)) |
| 386 self.end_headers() |
| 387 self.wfile.write(raw_reply) |
| 388 return True |
| 389 |
| 390 def ChromiumSyncEnableKeystoreEncryptionOpHandler(self): |
| 391 test_name = "/chromiumsync/enablekeystoreencryption" |
| 392 if not self._ShouldHandleRequest(test_name): |
| 393 return False |
| 394 result, raw_reply = ( |
| 395 self.server._sync_handler.HandleEnableKeystoreEncryption()) |
| 396 self.send_response(result) |
| 397 self.send_header('Content-Type', 'text/html') |
| 398 self.send_header('Content-Length', len(raw_reply)) |
| 399 self.end_headers() |
| 400 self.wfile.write(raw_reply) |
| 401 return True |
| 402 |
| 403 def ChromiumSyncRotateKeystoreKeysOpHandler(self): |
| 404 test_name = "/chromiumsync/rotatekeystorekeys" |
| 405 if not self._ShouldHandleRequest(test_name): |
| 406 return False |
| 407 result, raw_reply = ( |
| 408 self.server._sync_handler.HandleRotateKeystoreKeys()) |
| 409 self.send_response(result) |
| 410 self.send_header('Content-Type', 'text/html') |
| 411 self.send_header('Content-Length', len(raw_reply)) |
| 412 self.end_headers() |
| 413 self.wfile.write(raw_reply) |
| 414 return True |
| 415 |
| 416 |
| 417 class SyncServerRunner(testserver_base.TestServerRunner): |
| 418 """TestServerRunner for the net test servers.""" |
| 419 |
| 420 def __init__(self): |
| 421 super(SyncServerRunner, self).__init__() |
| 422 |
| 423 def create_server(self, server_data): |
| 424 port = self.options.port |
| 425 host = self.options.host |
| 426 xmpp_port = self.options.xmpp_port |
| 427 server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler) |
| 428 print 'Sync HTTP server started on port %d...' % server.server_port |
| 429 print 'Sync XMPP server started on port %d...' % server.xmpp_port |
| 430 server_data['port'] = server.server_port |
| 431 server_data['xmpp_port'] = server.xmpp_port |
| 432 return server |
| 433 |
| 434 def run_server(self): |
| 435 testserver_base.TestServerRunner.run_server(self) |
| 436 |
| 437 def add_options(self): |
| 438 testserver_base.TestServerRunner.add_options(self) |
| 439 self.option_parser.add_option('--xmpp-port', default='0', type='int', |
| 440 help='Port used by the XMPP server. If ' |
| 441 'unspecified, the XMPP server will listen on ' |
| 442 'an ephemeral port.') |
| 443 # Override the default logfile name used in testserver.py. |
| 444 self.option_parser.set_defaults(log_file='sync_testserver.log') |
| 445 |
| 446 if __name__ == '__main__': |
| 447 sys.exit(SyncServerRunner().main()) |
OLD | NEW |