Home | History | Annotate | Download | only in testserver
      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                     self.ChromiumSyncEnableManagedUserAcknowledgementHandler,
    154                     self.ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler,
    155                     self.GaiaOAuth2TokenHandler,
    156                     self.GaiaSetOAuth2TokenResponseHandler,
    157                     self.TriggerSyncedNotificationHandler,
    158                     self.SyncedNotificationsPageHandler,
    159                     self.TriggerSyncedNotificationAppInfoHandler,
    160                     self.SyncedNotificationsAppInfoPageHandler,
    161                     self.CustomizeClientCommandHandler]
    162 
    163     post_handlers = [self.ChromiumSyncCommandHandler,
    164                      self.ChromiumSyncTimeHandler,
    165                      self.GaiaOAuth2TokenHandler,
    166                      self.GaiaSetOAuth2TokenResponseHandler]
    167     testserver_base.BasePageHandler.__init__(self, request, client_address,
    168                                              sync_http_server, [], get_handlers,
    169                                              [], post_handlers, [])
    170 
    171 
    172   def ChromiumSyncTimeHandler(self):
    173     """Handle Chromium sync .../time requests.
    174 
    175     The syncer sometimes checks server reachability by examining /time.
    176     """
    177 
    178     test_name = "/chromiumsync/time"
    179     if not self._ShouldHandleRequest(test_name):
    180       return False
    181 
    182     # Chrome hates it if we send a response before reading the request.
    183     if self.headers.getheader('content-length'):
    184       length = int(self.headers.getheader('content-length'))
    185       _raw_request = self.rfile.read(length)
    186 
    187     self.send_response(200)
    188     self.send_header('Content-Type', 'text/plain')
    189     self.end_headers()
    190     self.wfile.write('0123456789')
    191     return True
    192 
    193   def ChromiumSyncCommandHandler(self):
    194     """Handle a chromiumsync command arriving via http.
    195 
    196     This covers all sync protocol commands: authentication, getupdates, and
    197     commit.
    198     """
    199 
    200     test_name = "/chromiumsync/command"
    201     if not self._ShouldHandleRequest(test_name):
    202       return False
    203 
    204     length = int(self.headers.getheader('content-length'))
    205     raw_request = self.rfile.read(length)
    206     http_response = 200
    207     raw_reply = None
    208     if not self.server.GetAuthenticated():
    209       http_response = 401
    210       challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
    211         self.server.server_address[0])
    212     else:
    213       http_response, raw_reply = self.server.HandleCommand(
    214           self.path, raw_request)
    215 
    216     ### Now send the response to the client. ###
    217     self.send_response(http_response)
    218     if http_response == 401:
    219       self.send_header('www-Authenticate', challenge)
    220     self.end_headers()
    221     self.wfile.write(raw_reply)
    222     return True
    223 
    224   def ChromiumSyncMigrationOpHandler(self):
    225     test_name = "/chromiumsync/migrate"
    226     if not self._ShouldHandleRequest(test_name):
    227       return False
    228 
    229     http_response, raw_reply = self.server._sync_handler.HandleMigrate(
    230         self.path)
    231     self.send_response(http_response)
    232     self.send_header('Content-Type', 'text/html')
    233     self.send_header('Content-Length', len(raw_reply))
    234     self.end_headers()
    235     self.wfile.write(raw_reply)
    236     return True
    237 
    238   def ChromiumSyncCredHandler(self):
    239     test_name = "/chromiumsync/cred"
    240     if not self._ShouldHandleRequest(test_name):
    241       return False
    242     try:
    243       query = urlparse.urlparse(self.path)[4]
    244       cred_valid = urlparse.parse_qs(query)['valid']
    245       if cred_valid[0] == 'True':
    246         self.server.SetAuthenticated(True)
    247       else:
    248         self.server.SetAuthenticated(False)
    249     except Exception:
    250       self.server.SetAuthenticated(False)
    251 
    252     http_response = 200
    253     raw_reply = 'Authenticated: %s ' % self.server.GetAuthenticated()
    254     self.send_response(http_response)
    255     self.send_header('Content-Type', 'text/html')
    256     self.send_header('Content-Length', len(raw_reply))
    257     self.end_headers()
    258     self.wfile.write(raw_reply)
    259     return True
    260 
    261   def ChromiumSyncXmppCredHandler(self):
    262     test_name = "/chromiumsync/xmppcred"
    263     if not self._ShouldHandleRequest(test_name):
    264       return False
    265     xmpp_server = self.server.GetXmppServer()
    266     try:
    267       query = urlparse.urlparse(self.path)[4]
    268       cred_valid = urlparse.parse_qs(query)['valid']
    269       if cred_valid[0] == 'True':
    270         xmpp_server.SetAuthenticated(True)
    271       else:
    272         xmpp_server.SetAuthenticated(False)
    273     except:
    274       xmpp_server.SetAuthenticated(False)
    275 
    276     http_response = 200
    277     raw_reply = 'XMPP Authenticated: %s ' % xmpp_server.GetAuthenticated()
    278     self.send_response(http_response)
    279     self.send_header('Content-Type', 'text/html')
    280     self.send_header('Content-Length', len(raw_reply))
    281     self.end_headers()
    282     self.wfile.write(raw_reply)
    283     return True
    284 
    285   def ChromiumSyncDisableNotificationsOpHandler(self):
    286     test_name = "/chromiumsync/disablenotifications"
    287     if not self._ShouldHandleRequest(test_name):
    288       return False
    289     self.server.GetXmppServer().DisableNotifications()
    290     result = 200
    291     raw_reply = ('<html><title>Notifications disabled</title>'
    292                  '<H1>Notifications disabled</H1></html>')
    293     self.send_response(result)
    294     self.send_header('Content-Type', 'text/html')
    295     self.send_header('Content-Length', len(raw_reply))
    296     self.end_headers()
    297     self.wfile.write(raw_reply)
    298     return True
    299 
    300   def ChromiumSyncEnableNotificationsOpHandler(self):
    301     test_name = "/chromiumsync/enablenotifications"
    302     if not self._ShouldHandleRequest(test_name):
    303       return False
    304     self.server.GetXmppServer().EnableNotifications()
    305     result = 200
    306     raw_reply = ('<html><title>Notifications enabled</title>'
    307                  '<H1>Notifications enabled</H1></html>')
    308     self.send_response(result)
    309     self.send_header('Content-Type', 'text/html')
    310     self.send_header('Content-Length', len(raw_reply))
    311     self.end_headers()
    312     self.wfile.write(raw_reply)
    313     return True
    314 
    315   def ChromiumSyncSendNotificationOpHandler(self):
    316     test_name = "/chromiumsync/sendnotification"
    317     if not self._ShouldHandleRequest(test_name):
    318       return False
    319     query = urlparse.urlparse(self.path)[4]
    320     query_params = urlparse.parse_qs(query)
    321     channel = ''
    322     data = ''
    323     if 'channel' in query_params:
    324       channel = query_params['channel'][0]
    325     if 'data' in query_params:
    326       data = query_params['data'][0]
    327     self.server.GetXmppServer().SendNotification(channel, data)
    328     result = 200
    329     raw_reply = ('<html><title>Notification sent</title>'
    330                  '<H1>Notification sent with channel "%s" '
    331                  'and data "%s"</H1></html>'
    332                  % (channel, data))
    333     self.send_response(result)
    334     self.send_header('Content-Type', 'text/html')
    335     self.send_header('Content-Length', len(raw_reply))
    336     self.end_headers()
    337     self.wfile.write(raw_reply)
    338     return True
    339 
    340   def ChromiumSyncBirthdayErrorOpHandler(self):
    341     test_name = "/chromiumsync/birthdayerror"
    342     if not self._ShouldHandleRequest(test_name):
    343       return False
    344     result, raw_reply = self.server._sync_handler.HandleCreateBirthdayError()
    345     self.send_response(result)
    346     self.send_header('Content-Type', 'text/html')
    347     self.send_header('Content-Length', len(raw_reply))
    348     self.end_headers()
    349     self.wfile.write(raw_reply)
    350     return True
    351 
    352   def ChromiumSyncTransientErrorOpHandler(self):
    353     test_name = "/chromiumsync/transienterror"
    354     if not self._ShouldHandleRequest(test_name):
    355       return False
    356     result, raw_reply = self.server._sync_handler.HandleSetTransientError()
    357     self.send_response(result)
    358     self.send_header('Content-Type', 'text/html')
    359     self.send_header('Content-Length', len(raw_reply))
    360     self.end_headers()
    361     self.wfile.write(raw_reply)
    362     return True
    363 
    364   def ChromiumSyncErrorOpHandler(self):
    365     test_name = "/chromiumsync/error"
    366     if not self._ShouldHandleRequest(test_name):
    367       return False
    368     result, raw_reply = self.server._sync_handler.HandleSetInducedError(
    369         self.path)
    370     self.send_response(result)
    371     self.send_header('Content-Type', 'text/html')
    372     self.send_header('Content-Length', len(raw_reply))
    373     self.end_headers()
    374     self.wfile.write(raw_reply)
    375     return True
    376 
    377   def ChromiumSyncSyncTabFaviconsOpHandler(self):
    378     test_name = "/chromiumsync/synctabfavicons"
    379     if not self._ShouldHandleRequest(test_name):
    380       return False
    381     result, raw_reply = self.server._sync_handler.HandleSetSyncTabFavicons()
    382     self.send_response(result)
    383     self.send_header('Content-Type', 'text/html')
    384     self.send_header('Content-Length', len(raw_reply))
    385     self.end_headers()
    386     self.wfile.write(raw_reply)
    387     return True
    388 
    389   def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
    390     test_name = "/chromiumsync/createsyncedbookmarks"
    391     if not self._ShouldHandleRequest(test_name):
    392       return False
    393     result, raw_reply = self.server._sync_handler.HandleCreateSyncedBookmarks()
    394     self.send_response(result)
    395     self.send_header('Content-Type', 'text/html')
    396     self.send_header('Content-Length', len(raw_reply))
    397     self.end_headers()
    398     self.wfile.write(raw_reply)
    399     return True
    400 
    401   def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
    402     test_name = "/chromiumsync/enablekeystoreencryption"
    403     if not self._ShouldHandleRequest(test_name):
    404       return False
    405     result, raw_reply = (
    406         self.server._sync_handler.HandleEnableKeystoreEncryption())
    407     self.send_response(result)
    408     self.send_header('Content-Type', 'text/html')
    409     self.send_header('Content-Length', len(raw_reply))
    410     self.end_headers()
    411     self.wfile.write(raw_reply)
    412     return True
    413 
    414   def ChromiumSyncRotateKeystoreKeysOpHandler(self):
    415     test_name = "/chromiumsync/rotatekeystorekeys"
    416     if not self._ShouldHandleRequest(test_name):
    417       return False
    418     result, raw_reply = (
    419         self.server._sync_handler.HandleRotateKeystoreKeys())
    420     self.send_response(result)
    421     self.send_header('Content-Type', 'text/html')
    422     self.send_header('Content-Length', len(raw_reply))
    423     self.end_headers()
    424     self.wfile.write(raw_reply)
    425     return True
    426 
    427   def ChromiumSyncEnableManagedUserAcknowledgementHandler(self):
    428     test_name = "/chromiumsync/enablemanageduseracknowledgement"
    429     if not self._ShouldHandleRequest(test_name):
    430       return False
    431     result, raw_reply = (
    432         self.server._sync_handler.HandleEnableManagedUserAcknowledgement())
    433     self.send_response(result)
    434     self.send_header('Content-Type', 'text/html')
    435     self.send_header('Content-Length', len(raw_reply))
    436     self.end_headers()
    437     self.wfile.write(raw_reply)
    438     return True
    439 
    440   def ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler(self):
    441     test_name = "/chromiumsync/enableprecommitgetupdateavoidance"
    442     if not self._ShouldHandleRequest(test_name):
    443       return False
    444     result, raw_reply = (
    445         self.server._sync_handler.HandleEnablePreCommitGetUpdateAvoidance())
    446     self.send_response(result)
    447     self.send_header('Content-Type', 'text/html')
    448     self.send_header('Content-Length', len(raw_reply))
    449     self.end_headers()
    450     self.wfile.write(raw_reply)
    451     return True
    452 
    453   def GaiaOAuth2TokenHandler(self):
    454     test_name = "/o/oauth2/token"
    455     if not self._ShouldHandleRequest(test_name):
    456       return False
    457     if self.headers.getheader('content-length'):
    458       length = int(self.headers.getheader('content-length'))
    459       _raw_request = self.rfile.read(length)
    460     result, raw_reply = (
    461         self.server._sync_handler.HandleGetOauth2Token())
    462     self.send_response(result)
    463     self.send_header('Content-Type', 'application/json')
    464     self.send_header('Content-Length', len(raw_reply))
    465     self.end_headers()
    466     self.wfile.write(raw_reply)
    467     return True
    468 
    469   def GaiaSetOAuth2TokenResponseHandler(self):
    470     test_name = "/setfakeoauth2token"
    471     if not self._ShouldHandleRequest(test_name):
    472       return False
    473 
    474     # The index of 'query' is 4.
    475     # See http://docs.python.org/2/library/urlparse.html
    476     query = urlparse.urlparse(self.path)[4]
    477     query_params = urlparse.parse_qs(query)
    478 
    479     response_code = 0
    480     request_token = ''
    481     access_token = ''
    482     expires_in = 0
    483     token_type = ''
    484 
    485     if 'response_code' in query_params:
    486       response_code = query_params['response_code'][0]
    487     if 'request_token' in query_params:
    488       request_token = query_params['request_token'][0]
    489     if 'access_token' in query_params:
    490       access_token = query_params['access_token'][0]
    491     if 'expires_in' in query_params:
    492       expires_in = query_params['expires_in'][0]
    493     if 'token_type' in query_params:
    494       token_type = query_params['token_type'][0]
    495 
    496     result, raw_reply = (
    497         self.server._sync_handler.HandleSetOauth2Token(
    498             response_code, request_token, access_token, expires_in, token_type))
    499     self.send_response(result)
    500     self.send_header('Content-Type', 'text/html')
    501     self.send_header('Content-Length', len(raw_reply))
    502     self.end_headers()
    503     self.wfile.write(raw_reply)
    504     return True
    505 
    506   def TriggerSyncedNotificationHandler(self):
    507     test_name = "/triggersyncednotification"
    508     if not self._ShouldHandleRequest(test_name):
    509       return False
    510 
    511     query = urlparse.urlparse(self.path)[4]
    512     query_params = urlparse.parse_qs(query)
    513 
    514     serialized_notification = ''
    515 
    516     if 'serialized_notification' in query_params:
    517       serialized_notification = query_params['serialized_notification'][0]
    518 
    519     try:
    520       notification_string = self.server._sync_handler.account \
    521           .AddSyncedNotification(serialized_notification)
    522       reply = "A synced notification was triggered:\n\n"
    523       reply += "<code>{}</code>.".format(notification_string)
    524       response_code = 200
    525     except chromiumsync.ClientNotConnectedError:
    526       reply = ('The client is not connected to the server, so the notification'
    527                ' could not be created.')
    528       response_code = 400
    529 
    530     self.send_response(response_code)
    531     self.send_header('Content-Type', 'text/html')
    532     self.send_header('Content-Length', len(reply))
    533     self.end_headers()
    534     self.wfile.write(reply)
    535     return True
    536 
    537   def TriggerSyncedNotificationAppInfoHandler(self):
    538     test_name = "/triggersyncednotificationappinfo"
    539     if not self._ShouldHandleRequest(test_name):
    540       return False
    541 
    542     query = urlparse.urlparse(self.path)[4]
    543     query_params = urlparse.parse_qs(query)
    544 
    545     app_info = ''
    546 
    547     if 'synced_notification_app_info' in query_params:
    548       app_info = query_params['synced_notification_app_info'][0]
    549 
    550     try:
    551       app_info_string = self.server._sync_handler.account \
    552           .AddSyncedNotificationAppInfo(app_info)
    553       reply = "A synced notification app info was sent:\n\n"
    554       reply += "<code>{}</code>.".format(app_info_string)
    555       response_code = 200
    556     except chromiumsync.ClientNotConnectedError:
    557       reply = ('The client is not connected to the server, so the app info'
    558                ' could not be created.')
    559       response_code = 400
    560 
    561     self.send_response(response_code)
    562     self.send_header('Content-Type', 'text/html')
    563     self.send_header('Content-Length', len(reply))
    564     self.end_headers()
    565     self.wfile.write(reply)
    566     return True
    567 
    568   def CustomizeClientCommandHandler(self):
    569     test_name = "/customizeclientcommand"
    570     if not self._ShouldHandleRequest(test_name):
    571       return False
    572 
    573     query = urlparse.urlparse(self.path)[4]
    574     query_params = urlparse.parse_qs(query)
    575 
    576     if 'sessions_commit_delay_seconds' in query_params:
    577       sessions_commit_delay = query_params['sessions_commit_delay_seconds'][0]
    578       try:
    579         command_string = self.server._sync_handler.CustomizeClientCommand(
    580             int(sessions_commit_delay))
    581         response_code = 200
    582         reply = "The ClientCommand was customized:\n\n"
    583         reply += "<code>{}</code>.".format(command_string)
    584       except ValueError:
    585         response_code = 400
    586         reply = "sessions_commit_delay_seconds was not an int"
    587     else:
    588       response_code = 400
    589       reply = "sessions_commit_delay_seconds is required"
    590 
    591     self.send_response(response_code)
    592     self.send_header('Content-Type', 'text/html')
    593     self.send_header('Content-Length', len(reply))
    594     self.end_headers()
    595     self.wfile.write(reply)
    596     return True
    597 
    598   def SyncedNotificationsPageHandler(self):
    599     test_name = "/syncednotifications"
    600     if not self._ShouldHandleRequest(test_name):
    601       return False
    602 
    603     html = open('sync/tools/testserver/synced_notifications.html', 'r').read()
    604 
    605     self.send_response(200)
    606     self.send_header('Content-Type', 'text/html')
    607     self.send_header('Content-Length', len(html))
    608     self.end_headers()
    609     self.wfile.write(html)
    610     return True
    611 
    612   def SyncedNotificationsAppInfoPageHandler(self):
    613     test_name = "/syncednotificationsappinfo"
    614     if not self._ShouldHandleRequest(test_name):
    615       return False
    616 
    617     html = \
    618       open('sync/tools/testserver/synced_notification_app_info.html', 'r').\
    619       read()
    620 
    621     self.send_response(200)
    622     self.send_header('Content-Type', 'text/html')
    623     self.send_header('Content-Length', len(html))
    624     self.end_headers()
    625     self.wfile.write(html)
    626     return True
    627 
    628 class SyncServerRunner(testserver_base.TestServerRunner):
    629   """TestServerRunner for the net test servers."""
    630 
    631   def __init__(self):
    632     super(SyncServerRunner, self).__init__()
    633 
    634   def create_server(self, server_data):
    635     port = self.options.port
    636     host = self.options.host
    637     xmpp_port = self.options.xmpp_port
    638     server = SyncHTTPServer((host, port), xmpp_port, SyncPageHandler)
    639     print ('Sync HTTP server started at %s:%d/chromiumsync...' %
    640            (host, server.server_port))
    641     print ('Fake OAuth2 Token server started at %s:%d/o/oauth2/token...' %
    642            (host, server.server_port))
    643     print ('Sync XMPP server started at %s:%d...' %
    644            (host, server.xmpp_port))
    645     server_data['port'] = server.server_port
    646     server_data['xmpp_port'] = server.xmpp_port
    647     return server
    648 
    649   def run_server(self):
    650     testserver_base.TestServerRunner.run_server(self)
    651 
    652   def add_options(self):
    653     testserver_base.TestServerRunner.add_options(self)
    654     self.option_parser.add_option('--xmpp-port', default='0', type='int',
    655                                   help='Port used by the XMPP server. If '
    656                                   'unspecified, the XMPP server will listen on '
    657                                   'an ephemeral port.')
    658     # Override the default logfile name used in testserver.py.
    659     self.option_parser.set_defaults(log_file='sync_testserver.log')
    660 
    661 if __name__ == '__main__':
    662   sys.exit(SyncServerRunner().main())
    663