Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / sync / tools / testserver / sync_testserver.py
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())