- add sources.
[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
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, [])
161
162
163   def ChromiumSyncTimeHandler(self):
164     """Handle Chromium sync .../time requests.
165
166     The syncer sometimes checks server reachability by examining /time.
167     """
168
169     test_name = "/chromiumsync/time"
170     if not self._ShouldHandleRequest(test_name):
171       return False
172
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)
177
178     self.send_response(200)
179     self.send_header('Content-Type', 'text/plain')
180     self.end_headers()
181     self.wfile.write('0123456789')
182     return True
183
184   def ChromiumSyncCommandHandler(self):
185     """Handle a chromiumsync command arriving via http.
186
187     This covers all sync protocol commands: authentication, getupdates, and
188     commit.
189     """
190
191     test_name = "/chromiumsync/command"
192     if not self._ShouldHandleRequest(test_name):
193       return False
194
195     length = int(self.headers.getheader('content-length'))
196     raw_request = self.rfile.read(length)
197     http_response = 200
198     raw_reply = None
199     if not self.server.GetAuthenticated():
200       http_response = 401
201       challenge = 'GoogleLogin realm="http://%s", service="chromiumsync"' % (
202         self.server.server_address[0])
203     else:
204       http_response, raw_reply = self.server.HandleCommand(
205           self.path, raw_request)
206
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)
211     self.end_headers()
212     self.wfile.write(raw_reply)
213     return True
214
215   def ChromiumSyncMigrationOpHandler(self):
216     test_name = "/chromiumsync/migrate"
217     if not self._ShouldHandleRequest(test_name):
218       return False
219
220     http_response, raw_reply = self.server._sync_handler.HandleMigrate(
221         self.path)
222     self.send_response(http_response)
223     self.send_header('Content-Type', 'text/html')
224     self.send_header('Content-Length', len(raw_reply))
225     self.end_headers()
226     self.wfile.write(raw_reply)
227     return True
228
229   def ChromiumSyncCredHandler(self):
230     test_name = "/chromiumsync/cred"
231     if not self._ShouldHandleRequest(test_name):
232       return False
233     try:
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)
238       else:
239         self.server.SetAuthenticated(False)
240     except Exception:
241       self.server.SetAuthenticated(False)
242
243     http_response = 200
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))
248     self.end_headers()
249     self.wfile.write(raw_reply)
250     return True
251
252   def ChromiumSyncXmppCredHandler(self):
253     test_name = "/chromiumsync/xmppcred"
254     if not self._ShouldHandleRequest(test_name):
255       return False
256     xmpp_server = self.server.GetXmppServer()
257     try:
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)
262       else:
263         xmpp_server.SetAuthenticated(False)
264     except:
265       xmpp_server.SetAuthenticated(False)
266
267     http_response = 200
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))
272     self.end_headers()
273     self.wfile.write(raw_reply)
274     return True
275
276   def ChromiumSyncDisableNotificationsOpHandler(self):
277     test_name = "/chromiumsync/disablenotifications"
278     if not self._ShouldHandleRequest(test_name):
279       return False
280     self.server.GetXmppServer().DisableNotifications()
281     result = 200
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))
287     self.end_headers()
288     self.wfile.write(raw_reply)
289     return True
290
291   def ChromiumSyncEnableNotificationsOpHandler(self):
292     test_name = "/chromiumsync/enablenotifications"
293     if not self._ShouldHandleRequest(test_name):
294       return False
295     self.server.GetXmppServer().EnableNotifications()
296     result = 200
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))
302     self.end_headers()
303     self.wfile.write(raw_reply)
304     return True
305
306   def ChromiumSyncSendNotificationOpHandler(self):
307     test_name = "/chromiumsync/sendnotification"
308     if not self._ShouldHandleRequest(test_name):
309       return False
310     query = urlparse.urlparse(self.path)[4]
311     query_params = urlparse.parse_qs(query)
312     channel = ''
313     data = ''
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)
319     result = 200
320     raw_reply = ('<html><title>Notification sent</title>'
321                  '<H1>Notification sent with channel "%s" '
322                  'and data "%s"</H1></html>'
323                  % (channel, data))
324     self.send_response(result)
325     self.send_header('Content-Type', 'text/html')
326     self.send_header('Content-Length', len(raw_reply))
327     self.end_headers()
328     self.wfile.write(raw_reply)
329     return True
330
331   def ChromiumSyncBirthdayErrorOpHandler(self):
332     test_name = "/chromiumsync/birthdayerror"
333     if not self._ShouldHandleRequest(test_name):
334       return False
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))
339     self.end_headers()
340     self.wfile.write(raw_reply)
341     return True
342
343   def ChromiumSyncTransientErrorOpHandler(self):
344     test_name = "/chromiumsync/transienterror"
345     if not self._ShouldHandleRequest(test_name):
346       return False
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))
351     self.end_headers()
352     self.wfile.write(raw_reply)
353     return True
354
355   def ChromiumSyncErrorOpHandler(self):
356     test_name = "/chromiumsync/error"
357     if not self._ShouldHandleRequest(test_name):
358       return False
359     result, raw_reply = self.server._sync_handler.HandleSetInducedError(
360         self.path)
361     self.send_response(result)
362     self.send_header('Content-Type', 'text/html')
363     self.send_header('Content-Length', len(raw_reply))
364     self.end_headers()
365     self.wfile.write(raw_reply)
366     return True
367
368   def ChromiumSyncSyncTabFaviconsOpHandler(self):
369     test_name = "/chromiumsync/synctabfavicons"
370     if not self._ShouldHandleRequest(test_name):
371       return False
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))
376     self.end_headers()
377     self.wfile.write(raw_reply)
378     return True
379
380   def ChromiumSyncCreateSyncedBookmarksOpHandler(self):
381     test_name = "/chromiumsync/createsyncedbookmarks"
382     if not self._ShouldHandleRequest(test_name):
383       return False
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))
388     self.end_headers()
389     self.wfile.write(raw_reply)
390     return True
391
392   def ChromiumSyncEnableKeystoreEncryptionOpHandler(self):
393     test_name = "/chromiumsync/enablekeystoreencryption"
394     if not self._ShouldHandleRequest(test_name):
395       return False
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))
401     self.end_headers()
402     self.wfile.write(raw_reply)
403     return True
404
405   def ChromiumSyncRotateKeystoreKeysOpHandler(self):
406     test_name = "/chromiumsync/rotatekeystorekeys"
407     if not self._ShouldHandleRequest(test_name):
408       return False
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))
414     self.end_headers()
415     self.wfile.write(raw_reply)
416     return True
417
418   def ChromiumSyncEnableManagedUserAcknowledgementHandler(self):
419     test_name = "/chromiumsync/enablemanageduseracknowledgement"
420     if not self._ShouldHandleRequest(test_name):
421       return False
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))
427     self.end_headers()
428     self.wfile.write(raw_reply)
429     return True
430
431   def ChromiumSyncEnablePreCommitGetUpdateAvoidanceHandler(self):
432     test_name = "/chromiumsync/enableprecommitgetupdateavoidance"
433     if not self._ShouldHandleRequest(test_name):
434       return False
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))
440     self.end_headers()
441     self.wfile.write(raw_reply)
442     return True
443
444 class SyncServerRunner(testserver_base.TestServerRunner):
445   """TestServerRunner for the net test servers."""
446
447   def __init__(self):
448     super(SyncServerRunner, self).__init__()
449
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
459     return server
460
461   def run_server(self):
462     testserver_base.TestServerRunner.run_server(self)
463
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')
472
473 if __name__ == '__main__':
474   sys.exit(SyncServerRunner().main())