try: from cStringIO import StringIO except ImportError: from StringIO import StringIO import xmlrpclib import sys import posixpath from trac.core import * from trac.perm import IPermissionRequestor from trac.web.main import IRequestHandler from trac.wiki.api import WikiSystem from trac.wiki.model import WikiPage from trac.wiki.formatter import wiki_to_html from trac.attachment import Attachment RPC_TYPES = {int: 'int', bool: 'boolean', str: 'string', float: 'double', xmlrpclib.DateTime: 'dateTime.iso8601', xmlrpclib.Binary: 'base64', list: 'array', dict: 'struct'} class IXmlRpcHandler(Interface): def xmlrpc_module(): pass def xmlrpc_methods(): pass class AbstractRpcHandler(Component): implements(IXmlRpcHandler) abstract = True def _init_methods(self): import inspect self._rpc_methods = {} for name, val in inspect.getmembers(self): if hasattr(val, '_xml_rpc_signatures'): self._rpc_methods[name] = val._xml_rpc_signatures self.log.debug('init: %s %s' % (self.__class__, self._rpc_methods)) def xmlrpc_methods(self): if not hasattr(self, '_rpc_methods'): self._init_methods() return self._rpc_methods def expose_rpc(return_type, *arg_types): def decorator(func): if not hasattr(func, '_xml_rpc_signatures'): func._xml_rpc_signatures = [] func._xml_rpc_signatures.append((return_type,) + tuple(arg_types)) return func return decorator class XmlRpcModule(AbstractRpcHandler): implements(IPermissionRequestor, IRequestHandler) rpc_handlers = ExtensionPoint(IXmlRpcHandler) def get_permission_actions(self): return ['XML_RPC'] def match_request(self, req): return req.path_info == '/xmlrpc' def process_request(self, req): if req.get_header('Content-Type') != 'text/xml': return self.html_view(req) try: req.perm.assert_permission('XML_RPC') xml = req.read(int(req.get_header('Content-Length'))) params, method = xmlrpclib.loads(xml) result = self._call(method, params, req) if result is None: raise xmlrpclib.Fault(4, 'Method call did not return a value') result = xmlrpclib.dumps((result,), methodresponse=True) except: self.log.error(sys.exc_value) result = xmlrpclib.dumps(self._wrap_except()) req.send_response(200) req.send_header('Content-Type', 'text/xml') req.end_headers() req.write(result) def html_view(self, req): req.perm.assert_permission('XML_RPC') def _get_info(self, method): parts = method.split('.', 1) if len(parts) < 2: return None moduleName, methodName = parts module = None for rpc in self.rpc_handlers: if rpc.xmlrpc_module() == moduleName: module = rpc break if not module or methodName not in module.xmlrpc_methods(): return None return module, methodName def _get_method(self, method): info = self._get_info(method) if not info: return None module, methodName = info return getattr(module, methodName) def _get_sig(self, method): info = self._get_info(method) if not info: return None module, methodName = info return module.xmlrpc_methods()[methodName] def _call(self, methodName, params, req): func = self._get_method(methodName) if func: return func(req, *params) else: raise xmlrpclib.Fault(2, 'Unknown method') def _wrap_except(self): e = sys.exc_value if isinstance(e, xmlrpclib.Fault): return e return xmlrpclib.Fault(1, '%s:%s' % (sys.exc_type, e)) def xmlrpc_module(self): return 'system' @expose_rpc(list) def listMethods(self, req): methods = [] for rpc in self.rpc_handlers: module = rpc.xmlrpc_module() for method in rpc.xmlrpc_methods(): methods.append('.'.join((module, method))) return methods @expose_rpc(str, str) def methodHelp(self, req, methodName): func = self._get_method(methodName) if func: import pydoc return pydoc.getdoc(func) else: raise xmlrpclib.Fault(3, "Can't get help for unknown method: %s" % methodName) @expose_rpc(list, str) def methodSignature(self, req, methodName): sig = self._get_sig(methodName) return [', '.join([RPC_TYPES[t] for t in s]) for s in sig] @expose_rpc(list, dict) def multicall(self, req, callParams): results = [] for meth_sig in callParams: try: r = [self._call(**meth_sig)] except: r = self._wrap_except() results.append(r) return results class WikiRpc(AbstractRpcHandler): def __init__(self): self.wiki = WikiSystem(self.env) def xmlrpc_module(self): return 'wiki' def _to_timestamp(self, datetime): import time return time.mktime(time.strptime(datetime.value, '%Y%m%dT%H:%M:%S')) def _page_info(self, name, time, author, version): return dict(name=name, lastModified=xmlrpclib.DateTime(int(time)), author=author, version=int(version)) @expose_rpc(dict, xmlrpclib.DateTime) def getRecentChanges(self, req, since): req.perm.assert_permission('WIKI_VIEW') since = self._to_timestamp(since) db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute('SELECT name, max(time), author, version FROM wiki' ' WHERE time >= %s GROUP BY name ORDER BY max(time) DESC', since) result = [] for name, time, author, version in cursor: result.append(self._page_info(name, time, author, version)) return result @expose_rpc(int) def getRPCVersionSupported(self, req): req.perm.assert_permission('WIKI_VIEW') return 2 @expose_rpc(str, str) @expose_rpc(str, str, int) def getPage(self, req, pagename, version=None): req.perm.assert_permission('WIKI_VIEW') page = WikiPage(self.env, pagename, version) if page.exists: return page.text else: msg = 'Wiki page "%s" does not exist' % pagename if version is not None: msg += ' at version %s' % version raise xmlrpclib.Fault(0, msg) getPageVersion = getPage @expose_rpc(str, str) @expose_rpc(str, str, int) def getPageHTML(self, req, pagename, version=None): req.perm.assert_permission('WIKI_VIEW') text = self.getPage(req, pagename, version) html = wiki_to_html(text, self.env, req, absurls=1) return '%s' % html getPageHTMLVersion = getPageHTML @expose_rpc(list) def getAllPages(self, req): ''' Returns a list of all pages. The result is an array of utf8 pagenames. ''' req.perm.assert_permission('WIKI_VIEW') return list(self.wiki.get_pages()) @expose_rpc(dict, str) @expose_rpc(dict, str, int) def getPageInfo(self, req, pagename, version=None): req.perm.assert_permission('WIKI_VIEW') page = WikiPage(self.env, pagename, version) if page.exists: return self._page_info(page.name, page.time, page.author, page.version) getPageInfoVersion = getPageInfo @expose_rpc(bool, str, str, dict) def putPage(self, req, pagename, content, attributes): page = WikiPage(self.env, pagename) if page.readonly: req.perm.assert_permission('WIKI_ADMIN') elif not page.exists: req.perm.assert_permission('WIKI_CREATE') else: req.perm.assert_permission('WIKI_MODIFY') page.text = content if req.perm.has_permission('WIKI_ADMIN'): page.readonly = attributes.get('readonly') and 1 or 0 page.save(attributes.get('author', req.authname), attributes.get('comment'), req.remote_addr) return True @expose_rpc(list, str) def listAttachments(self, req, pagename): req.perm.assert_permission('WIKI_VIEW') return [pagename + '/' + a.filename for a in Attachment.select(self.env, 'wiki', pagename)] @expose_rpc(xmlrpclib.Binary, str) def getAttachment(self, req, path): req.perm.assert_permission('WIKI_VIEW') pagename, filename = posixpath.split(path) attachment = Attachment(self.env, 'wiki', pagename, filename) return xmlrpclib.Binary(attachment.open().read()) @expose_rpc(bool, str, str, xmlrpclib.Binary) def putAttachment(self, req, path, data): req.perm.assert_permission('WIKI_MODIFY') pagename, filename = posixpath.split(path) if not WikiPage(self.env, pagename).exists: raise TracError, 'Wiki page "%s" does not exist' % pagename attachment = Attachment(self.env, 'wiki', pagename) attachment.insert(filename, StringIO(data.data), len(data.data)) return True def listLinks(self, req, pagename): req.perm.assert_permission('WIKI_VIEW') pass