Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / third_party / pywebsocket / src / mod_pywebsocket / dispatch.py
1 # Copyright 2012, Google Inc.
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions are
6 # met:
7 #
8 #     * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 #     * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following disclaimer
12 # in the documentation and/or other materials provided with the
13 # distribution.
14 #     * Neither the name of Google Inc. nor the names of its
15 # contributors may be used to endorse or promote products derived from
16 # this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """Dispatch WebSocket request.
32 """
33
34
35 import logging
36 import os
37 import re
38
39 from mod_pywebsocket import common
40 from mod_pywebsocket import handshake
41 from mod_pywebsocket import msgutil
42 from mod_pywebsocket import mux
43 from mod_pywebsocket import stream
44 from mod_pywebsocket import util
45
46
47 _SOURCE_PATH_PATTERN = re.compile(r'(?i)_wsh\.py$')
48 _SOURCE_SUFFIX = '_wsh.py'
49 _DO_EXTRA_HANDSHAKE_HANDLER_NAME = 'web_socket_do_extra_handshake'
50 _TRANSFER_DATA_HANDLER_NAME = 'web_socket_transfer_data'
51 _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME = (
52     'web_socket_passive_closing_handshake')
53
54
55 class DispatchException(Exception):
56     """Exception in dispatching WebSocket request."""
57
58     def __init__(self, name, status=common.HTTP_STATUS_NOT_FOUND):
59         super(DispatchException, self).__init__(name)
60         self.status = status
61
62
63 def _default_passive_closing_handshake_handler(request):
64     """Default web_socket_passive_closing_handshake handler."""
65
66     return common.STATUS_NORMAL_CLOSURE, ''
67
68
69 def _normalize_path(path):
70     """Normalize path.
71
72     Args:
73         path: the path to normalize.
74
75     Path is converted to the absolute path.
76     The input path can use either '\\' or '/' as the separator.
77     The normalized path always uses '/' regardless of the platform.
78     """
79
80     path = path.replace('\\', os.path.sep)
81     path = os.path.realpath(path)
82     path = path.replace('\\', '/')
83     return path
84
85
86 def _create_path_to_resource_converter(base_dir):
87     """Returns a function that converts the path of a WebSocket handler source
88     file to a resource string by removing the path to the base directory from
89     its head, removing _SOURCE_SUFFIX from its tail, and replacing path
90     separators in it with '/'.
91
92     Args:
93         base_dir: the path to the base directory.
94     """
95
96     base_dir = _normalize_path(base_dir)
97
98     base_len = len(base_dir)
99     suffix_len = len(_SOURCE_SUFFIX)
100
101     def converter(path):
102         if not path.endswith(_SOURCE_SUFFIX):
103             return None
104         # _normalize_path must not be used because resolving symlink breaks
105         # following path check.
106         path = path.replace('\\', '/')
107         if not path.startswith(base_dir):
108             return None
109         return path[base_len:-suffix_len]
110
111     return converter
112
113
114 def _enumerate_handler_file_paths(directory):
115     """Returns a generator that enumerates WebSocket Handler source file names
116     in the given directory.
117     """
118
119     for root, unused_dirs, files in os.walk(directory):
120         for base in files:
121             path = os.path.join(root, base)
122             if _SOURCE_PATH_PATTERN.search(path):
123                 yield path
124
125
126 class _HandlerSuite(object):
127     """A handler suite holder class."""
128
129     def __init__(self, do_extra_handshake, transfer_data,
130                  passive_closing_handshake):
131         self.do_extra_handshake = do_extra_handshake
132         self.transfer_data = transfer_data
133         self.passive_closing_handshake = passive_closing_handshake
134
135
136 def _source_handler_file(handler_definition):
137     """Source a handler definition string.
138
139     Args:
140         handler_definition: a string containing Python statements that define
141                             handler functions.
142     """
143
144     global_dic = {}
145     try:
146         exec handler_definition in global_dic
147     except Exception:
148         raise DispatchException('Error in sourcing handler:' +
149                                 util.get_stack_trace())
150     passive_closing_handshake_handler = None
151     try:
152         passive_closing_handshake_handler = _extract_handler(
153             global_dic, _PASSIVE_CLOSING_HANDSHAKE_HANDLER_NAME)
154     except Exception:
155         passive_closing_handshake_handler = (
156             _default_passive_closing_handshake_handler)
157     return _HandlerSuite(
158         _extract_handler(global_dic, _DO_EXTRA_HANDSHAKE_HANDLER_NAME),
159         _extract_handler(global_dic, _TRANSFER_DATA_HANDLER_NAME),
160         passive_closing_handshake_handler)
161
162
163 def _extract_handler(dic, name):
164     """Extracts a callable with the specified name from the given dictionary
165     dic.
166     """
167
168     if name not in dic:
169         raise DispatchException('%s is not defined.' % name)
170     handler = dic[name]
171     if not callable(handler):
172         raise DispatchException('%s is not callable.' % name)
173     return handler
174
175
176 class Dispatcher(object):
177     """Dispatches WebSocket requests.
178
179     This class maintains a map from resource name to handlers.
180     """
181
182     def __init__(
183         self, root_dir, scan_dir=None,
184         allow_handlers_outside_root_dir=True):
185         """Construct an instance.
186
187         Args:
188             root_dir: The directory where handler definition files are
189                       placed.
190             scan_dir: The directory where handler definition files are
191                       searched. scan_dir must be a directory under root_dir,
192                       including root_dir itself.  If scan_dir is None,
193                       root_dir is used as scan_dir. scan_dir can be useful
194                       in saving scan time when root_dir contains many
195                       subdirectories.
196             allow_handlers_outside_root_dir: Scans handler files even if their
197                       canonical path is not under root_dir.
198         """
199
200         self._logger = util.get_class_logger(self)
201
202         self._handler_suite_map = {}
203         self._source_warnings = []
204         if scan_dir is None:
205             scan_dir = root_dir
206         if not os.path.realpath(scan_dir).startswith(
207                 os.path.realpath(root_dir)):
208             raise DispatchException('scan_dir:%s must be a directory under '
209                                     'root_dir:%s.' % (scan_dir, root_dir))
210         self._source_handler_files_in_dir(
211             root_dir, scan_dir, allow_handlers_outside_root_dir)
212
213     def add_resource_path_alias(self,
214                                 alias_resource_path, existing_resource_path):
215         """Add resource path alias.
216
217         Once added, request to alias_resource_path would be handled by
218         handler registered for existing_resource_path.
219
220         Args:
221             alias_resource_path: alias resource path
222             existing_resource_path: existing resource path
223         """
224         try:
225             handler_suite = self._handler_suite_map[existing_resource_path]
226             self._handler_suite_map[alias_resource_path] = handler_suite
227         except KeyError:
228             raise DispatchException('No handler for: %r' %
229                                     existing_resource_path)
230
231     def source_warnings(self):
232         """Return warnings in sourcing handlers."""
233
234         return self._source_warnings
235
236     def do_extra_handshake(self, request):
237         """Do extra checking in WebSocket handshake.
238
239         Select a handler based on request.uri and call its
240         web_socket_do_extra_handshake function.
241
242         Args:
243             request: mod_python request.
244
245         Raises:
246             DispatchException: when handler was not found
247             AbortedByUserException: when user handler abort connection
248             HandshakeException: when opening handshake failed
249         """
250
251         handler_suite = self.get_handler_suite(request.ws_resource)
252         if handler_suite is None:
253             raise DispatchException('No handler for: %r' % request.ws_resource)
254         do_extra_handshake_ = handler_suite.do_extra_handshake
255         try:
256             do_extra_handshake_(request)
257         except handshake.AbortedByUserException, e:
258             raise
259         except Exception, e:
260             util.prepend_message_to_exception(
261                     '%s raised exception for %s: ' % (
262                             _DO_EXTRA_HANDSHAKE_HANDLER_NAME,
263                             request.ws_resource),
264                     e)
265             raise handshake.HandshakeException(e, common.HTTP_STATUS_FORBIDDEN)
266
267     def transfer_data(self, request):
268         """Let a handler transfer_data with a WebSocket client.
269
270         Select a handler based on request.ws_resource and call its
271         web_socket_transfer_data function.
272
273         Args:
274             request: mod_python request.
275
276         Raises:
277             DispatchException: when handler was not found
278             AbortedByUserException: when user handler abort connection
279         """
280
281         # TODO(tyoshino): Terminate underlying TCP connection if possible.
282         try:
283             if mux.use_mux(request):
284                 mux.start(request, self)
285             else:
286                 handler_suite = self.get_handler_suite(request.ws_resource)
287                 if handler_suite is None:
288                     raise DispatchException('No handler for: %r' %
289                                             request.ws_resource)
290                 transfer_data_ = handler_suite.transfer_data
291                 transfer_data_(request)
292
293             if not request.server_terminated:
294                 request.ws_stream.close_connection()
295         # Catch non-critical exceptions the handler didn't handle.
296         except handshake.AbortedByUserException, e:
297             self._logger.debug('%s', e)
298             raise
299         except msgutil.BadOperationException, e:
300             self._logger.debug('%s', e)
301             request.ws_stream.close_connection(common.STATUS_ABNORMAL_CLOSURE)
302         except msgutil.InvalidFrameException, e:
303             # InvalidFrameException must be caught before
304             # ConnectionTerminatedException that catches InvalidFrameException.
305             self._logger.debug('%s', e)
306             request.ws_stream.close_connection(common.STATUS_PROTOCOL_ERROR)
307         except msgutil.UnsupportedFrameException, e:
308             self._logger.debug('%s', e)
309             request.ws_stream.close_connection(common.STATUS_UNSUPPORTED_DATA)
310         except stream.InvalidUTF8Exception, e:
311             self._logger.debug('%s', e)
312             request.ws_stream.close_connection(
313                 common.STATUS_INVALID_FRAME_PAYLOAD_DATA)
314         except msgutil.ConnectionTerminatedException, e:
315             self._logger.debug('%s', e)
316         except Exception, e:
317             util.prepend_message_to_exception(
318                 '%s raised exception for %s: ' % (
319                     _TRANSFER_DATA_HANDLER_NAME, request.ws_resource),
320                 e)
321             raise
322
323     def passive_closing_handshake(self, request):
324         """Prepare code and reason for responding client initiated closing
325         handshake.
326         """
327
328         handler_suite = self.get_handler_suite(request.ws_resource)
329         if handler_suite is None:
330             return _default_passive_closing_handshake_handler(request)
331         return handler_suite.passive_closing_handshake(request)
332
333     def get_handler_suite(self, resource):
334         """Retrieves two handlers (one for extra handshake processing, and one
335         for data transfer) for the given request as a HandlerSuite object.
336         """
337
338         fragment = None
339         if '#' in resource:
340             resource, fragment = resource.split('#', 1)
341         if '?' in resource:
342             resource = resource.split('?', 1)[0]
343         handler_suite = self._handler_suite_map.get(resource)
344         if handler_suite and fragment:
345             raise DispatchException('Fragment identifiers MUST NOT be used on '
346                                     'WebSocket URIs',
347                                     common.HTTP_STATUS_BAD_REQUEST)
348         return handler_suite
349
350     def _source_handler_files_in_dir(
351         self, root_dir, scan_dir, allow_handlers_outside_root_dir):
352         """Source all the handler source files in the scan_dir directory.
353
354         The resource path is determined relative to root_dir.
355         """
356
357         # We build a map from resource to handler code assuming that there's
358         # only one path from root_dir to scan_dir and it can be obtained by
359         # comparing realpath of them.
360
361         # Here we cannot use abspath. See
362         # https://bugs.webkit.org/show_bug.cgi?id=31603
363
364         convert = _create_path_to_resource_converter(root_dir)
365         scan_realpath = os.path.realpath(scan_dir)
366         root_realpath = os.path.realpath(root_dir)
367         for path in _enumerate_handler_file_paths(scan_realpath):
368             if (not allow_handlers_outside_root_dir and
369                 (not os.path.realpath(path).startswith(root_realpath))):
370                 self._logger.debug(
371                     'Canonical path of %s is not under root directory' %
372                     path)
373                 continue
374             try:
375                 handler_suite = _source_handler_file(open(path).read())
376             except DispatchException, e:
377                 self._source_warnings.append('%s: %s' % (path, e))
378                 continue
379             resource = convert(path)
380             if resource is None:
381                 self._logger.debug(
382                     'Path to resource conversion on %s failed' % path)
383             else:
384                 self._handler_suite_map[convert(path)] = handler_suite
385
386
387 # vi:sts=4 sw=4 et