| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: utf-8 -*- |
|---|
| 3 | # |
|---|
| 4 | # Copyright (C) 2013 MATOBA Akihiro <matobaa+trac-hacks@gmail.com> |
|---|
| 5 | # All rights reserved. |
|---|
| 6 | # |
|---|
| 7 | # This software is licensed as described in the file COPYING, which |
|---|
| 8 | # you should have received as part of this distribution. |
|---|
| 9 | |
|---|
| 10 | # Tested on |
|---|
| 11 | # - Internet Explorer 10.0.9200.16618 |
|---|
| 12 | # - Google Chrome 27.0.1453.110 m |
|---|
| 13 | # - Firefox 18.0.1 |
|---|
| 14 | |
|---|
| 15 | from trac.core import Component, implements |
|---|
| 16 | from trac.web.api import RequestDone |
|---|
| 17 | from trac.config import Option |
|---|
| 18 | |
|---|
| 19 | try: |
|---|
| 20 | from tracrpc.api import IRPCProtocol, IXMLRPCHandler |
|---|
| 21 | from tracrpc.json_rpc import JsonRpcProtocol |
|---|
| 22 | |
|---|
| 23 | class CrossOriginResourceSharingEnabler(Component): |
|---|
| 24 | """Enable CORS access from another site for XmlRpcPlugin; see: http://www.w3.org/TR/cors/""" |
|---|
| 25 | implements(IRPCProtocol, IXMLRPCHandler) |
|---|
| 26 | |
|---|
| 27 | jsonrpc_origin = Option('rpc', 'jsonrpc_origin', '', |
|---|
| 28 | doc=u"""SP separated permitted origins; |
|---|
| 29 | Where do you want to permit requests from; |
|---|
| 30 | for jsonrpc Cross-Origin Resource Sharing. |
|---|
| 31 | http://example.com/path for specified, blank for nowhere, |
|---|
| 32 | {{{*}}}(asterisk) for anywhere to permit. |
|---|
| 33 | See: http://www.w3.org/TR/cors/, rfc:6454. |
|---|
| 34 | (Provided by !ContextChrome.!CrossOriginResourceSharingEnabler)""") |
|---|
| 35 | |
|---|
| 36 | wrapped = None |
|---|
| 37 | |
|---|
| 38 | # IRPCProtocol methods |
|---|
| 39 | def rpc_info(self): |
|---|
| 40 | return 'CORS', 'Cross Origin Resource Sharing Enabler' |
|---|
| 41 | |
|---|
| 42 | def rpc_match(self): |
|---|
| 43 | if not self.wrapped: |
|---|
| 44 | self.wrap() |
|---|
| 45 | yield ('jsonrpc', 'text/plain') |
|---|
| 46 | |
|---|
| 47 | def parse_rpc_request(self, req, content_type): |
|---|
| 48 | return {'method': 'CORS.dummy'} |
|---|
| 49 | |
|---|
| 50 | def send_rpc_result(self, req, result): |
|---|
| 51 | jsonrpc_origin = self.config.get('rpc', 'jsonrpc_origin') |
|---|
| 52 | req.send_response(200) |
|---|
| 53 | req.send_header('Content-Length', 0) |
|---|
| 54 | req.send_header('Access-Control-Allow-Origin', jsonrpc_origin) # req.get_header('Origin')) |
|---|
| 55 | req.send_header('Access-Control-Allow-Headers', 'Content-Type') |
|---|
| 56 | req.end_headers() |
|---|
| 57 | req.write('\n') |
|---|
| 58 | raise RequestDone() |
|---|
| 59 | |
|---|
| 60 | def send_rpc_error(self, req, e): |
|---|
| 61 | req.send_response(500) |
|---|
| 62 | req.send_header('Content-Length', 0) |
|---|
| 63 | req.end_headers() |
|---|
| 64 | req.write('\n') |
|---|
| 65 | raise RequestDone() |
|---|
| 66 | |
|---|
| 67 | #IXMLRPCHandler methods |
|---|
| 68 | def xmlrpc_namespace(self): |
|---|
| 69 | return 'CORS' |
|---|
| 70 | |
|---|
| 71 | def xmlrpc_methods(self): |
|---|
| 72 | yield ('TICKET_VIEW', ((None,),), lambda req: None, 'dummy') |
|---|
| 73 | |
|---|
| 74 | def wrap(self): |
|---|
| 75 | jsonRpcProtocol = self.compmgr[JsonRpcProtocol] |
|---|
| 76 | if not jsonRpcProtocol: |
|---|
| 77 | self.wrapped = 1 # avoid to wrap again |
|---|
| 78 | return |
|---|
| 79 | |
|---|
| 80 | def _send_response(*args, **kwargs): # hook method |
|---|
| 81 | jsonrpc_origin = self.config.get('rpc', 'jsonrpc_origin') |
|---|
| 82 | args[0].send_header('Access-Control-Allow-Origin', jsonrpc_origin) # args[0].get_header('Origin')) |
|---|
| 83 | return self.wrapped(*args, **kwargs) |
|---|
| 84 | self.wrapped = jsonRpcProtocol._send_response |
|---|
| 85 | jsonRpcProtocol._send_response = _send_response |
|---|
| 86 | |
|---|
| 87 | except: |
|---|
| 88 | class CrossOriginResourceSharingEnabler(Component): |
|---|
| 89 | """(Disabled; XmlRpcPlugin required. See http://trac-hacks.org/wiki/XmlRpcPlugin.)""" |
|---|
| 90 | pass |
|---|