• R/O
  • SSH

traclightning: Commit

traclightningのリポジトリ


Commit MetaInfo

Revision9be46ec8635a4d0d2470626ab074efb57fce7ccb (tree)
Time2011-05-30 19:44:56
Authorkanu_orz
Commiterkanu_orz

Log Message

update xmlrpcplugin to 1.1.2

Change Summary

Incremental Difference

diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/all-wcprops
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/all-wcprops Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,29 @@
1+K 25
2+svn:wc:ra_dav:version-url
3+V 37
4+/svn/!svn/ver/9970/xmlrpcplugin/trunk
5+END
6+README.wiki
7+K 25
8+svn:wc:ra_dav:version-url
9+V 49
10+/svn/!svn/ver/7916/xmlrpcplugin/trunk/README.wiki
11+END
12+setup.py
13+K 25
14+svn:wc:ra_dav:version-url
15+V 46
16+/svn/!svn/ver/9912/xmlrpcplugin/trunk/setup.py
17+END
18+MANIFEST.in
19+K 25
20+svn:wc:ra_dav:version-url
21+V 49
22+/svn/!svn/ver/7916/xmlrpcplugin/trunk/MANIFEST.in
23+END
24+setup.cfg
25+K 25
26+svn:wc:ra_dav:version-url
27+V 47
28+/svn/!svn/ver/7916/xmlrpcplugin/trunk/setup.cfg
29+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/dir-prop-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/dir-prop-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,8 @@
1+K 10
2+svn:ignore
3+V 22
4+build
5+dist
6+rpctestenv
7+
8+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/entries
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/entries Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,167 @@
1+10
2+
3+dir
4+10247
5+http://trac-hacks.org/svn/xmlrpcplugin/trunk
6+http://trac-hacks.org/svn
7+
8+
9+
10+2011-03-18T10:32:52.709335Z
11+9970
12+osimons
13+has-props
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+7322e99d-02ea-0310-aa39-e9a107903beb
28+
29+README.wiki
30+file
31+
32+
33+
34+
35+2010-05-02T23:49:30.648005Z
36+7349e9210f1c5664e6aec9d7c1236379
37+2010-05-02T23:49:30.648005Z
38+7916
39+osimons
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+
55+
56+
57+
58+
59+
60+
61+3164
62+
63+setup.py
64+file
65+
66+
67+
68+
69+2011-03-02T03:40:36.325896Z
70+ea55ef999127c930d13f0a9a5595bae5
71+2011-03-02T03:40:36.325896Z
72+9912
73+osimons
74+
75+
76+
77+
78+
79+
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
93+
94+
95+976
96+
97+MANIFEST.in
98+file
99+
100+
101+
102+
103+2010-05-02T23:49:30.648005Z
104+0d763c82345f2b417d66cea03ca6df94
105+2010-05-02T23:49:30.648005Z
106+7916
107+osimons
108+
109+
110+
111+
112+
113+
114+
115+
116+
117+
118+
119+
120+
121+
122+
123+
124+
125+
126+
127+
128+
129+128
130+
131+tracrpc
132+dir
133+
134+setup.cfg
135+file
136+
137+
138+
139+
140+2010-05-02T23:49:30.648005Z
141+4754f6eaa317fc35b7e24989a2b6307c
142+2010-05-02T23:49:30.648005Z
143+7916
144+osimons
145+
146+
147+
148+
149+
150+
151+
152+
153+
154+
155+
156+
157+
158+
159+
160+
161+
162+
163+
164+
165+
166+48
167+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/text-base/MANIFEST.in.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/text-base/MANIFEST.in.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,5 @@
1+include README.wiki
2+include setup.cfg
3+include tracrpc/htdocs/*.css
4+include tracrpc/htdocs/*.js
5+include tracrpc/templates/*.html
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/text-base/README.wiki.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/text-base/README.wiki.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,92 @@
1+= Trac RPC plugin =
2+
3+Remote Procedure Call interface for Trac.
4+
5+Protocols:
6+ * XML-RPC
7+ * JSON-RPC
8+
9+API support:
10+ * search
11+ * system
12+ * ticket
13+ * ticket.component
14+ * ticket.milestone
15+ * ticket.priority
16+ * ticket.resolution
17+ * ticket.severity
18+ * ticket.status
19+ * ticket.type
20+ * ticket.version
21+ * wiki
22+
23+== Installing and Using ==
24+
25+See http://trac-hacks.org/wiki/XmlRpcPlugin for details on how to install, how
26+get help, and how to report issues.
27+
28+== API Documentation ==
29+
30+The API documentation is available at `<project_url>/rpc` for projects that
31+have the plugin installed and enabled. It can be accessed by all users that
32+have been granted `XML_RPC` permission.
33+
34+== Development ==
35+
36+The Trac RPC plugin uses pluggable interfaces to do all its work. That means it
37+is easy to extend, and currently supports:
38+ * protocols; add a new protocol in addition to the builtin ones and read input
39+ and answer request in whatever form and format needed.
40+ * methods; adding new methods available for remote procedure calls that will
41+ work for any enabled protocol.
42+
43+See source for documentation. The source code can be obtained from:
44+
45+http://trac-hacks.org/svn/xmlrpcplugin/
46+
47+For work on the plugin itself (for submitting patches and more), please verify
48+patches by running unittests (requires Trac source code on path):
49+{{{
50+python setup.py test
51+}}}
52+
53+== Thanks ==
54+
55+Thanks to all those that use the plugin, and contribute with error reports,
56+and patches for bugs and enhancements. Special thanks to:
57+ * Matt Good
58+ * Steffen Pingel
59+ * Olemis Lang
60+
61+== License ==
62+
63+{{{
64+Copyright (c) 2005-2008, Alec Thomas (alec@swapoff.org)
65+Copyright (c) 2009, CodeResort.com/BV Network AS (simon-code@bvnetwork.no)
66+
67+All rights reserved.
68+
69+Redistribution and use in source and binary forms, with or without
70+modification, are permitted provided that the following conditions are met:
71+
72+ 1. Redistributions of source code must retain the above copyright notice,
73+ this list of conditions and the following disclaimer.
74+ 2. Redistributions in binary form must reproduce the above copyright notice,
75+ this list of conditions and the following disclaimer in the documentation
76+ and/or other materials provided with the distribution.
77+ 3. Neither the name of the copyright holder(s) nor the names of its
78+ contributors may be used to endorse or promote products derived from this
79+ software without specific prior written permission.
80+
81+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
82+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
83+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
84+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
85+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
86+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
87+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
88+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
89+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
90+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
91+THE POSSIBILITY OF SUCH DAMAGE.
92+}}}
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/text-base/setup.cfg.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/text-base/setup.cfg.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,3 @@
1+[egg_info]
2+tag_build =
3+tag_svn_revision = true
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/.svn/text-base/setup.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/.svn/text-base/setup.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,40 @@
1+#!/usr/bin/env python
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import sys
10+
11+from setuptools import setup, find_packages
12+
13+try :
14+ import crypt
15+except ImportError :
16+ test_deps = ['twill', 'fcrypt']
17+else :
18+ test_deps = ['twill']
19+
20+setup(
21+ name='TracXMLRPC',
22+ version='1.1.2',
23+ license='BSD',
24+ author='Alec Thomas',
25+ author_email='alec@swapoff.org',
26+ maintainer='Odd Simon Simonsen',
27+ maintainer_email='simon-code@bvnetwork.no',
28+ url='http://trac-hacks.org/wiki/XmlRpcPlugin',
29+ description='RPC interface to Trac',
30+ zip_safe=True,
31+ test_suite = 'tracrpc.tests.test_suite',
32+ tests_require = test_deps,
33+ packages=find_packages(exclude=['*.tests']),
34+ package_data={
35+ 'tracrpc': ['templates/*.html', 'htdocs/*.js', 'htdocs/*.css']
36+ },
37+ entry_points={
38+ 'trac.plugins': 'TracXMLRPC = tracrpc'
39+ },
40+ )
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/setup.py
--- a/plugins/svn/xmlrpcplugin/setup.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/setup.py Mon May 30 19:44:56 2011 +0900
@@ -19,7 +19,7 @@
1919
2020 setup(
2121 name='TracXMLRPC',
22- version='1.1.0',
22+ version='1.1.2',
2323 license='BSD',
2424 author='Alec Thomas',
2525 author_email='alec@swapoff.org',
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/all-wcprops
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/all-wcprops Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,59 @@
1+K 25
2+svn:wc:ra_dav:version-url
3+V 45
4+/svn/!svn/ver/9970/xmlrpcplugin/trunk/tracrpc
5+END
6+api.py
7+K 25
8+svn:wc:ra_dav:version-url
9+V 52
10+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/api.py
11+END
12+web_ui.py
13+K 25
14+svn:wc:ra_dav:version-url
15+V 55
16+/svn/!svn/ver/9970/xmlrpcplugin/trunk/tracrpc/web_ui.py
17+END
18+json_rpc.py
19+K 25
20+svn:wc:ra_dav:version-url
21+V 57
22+/svn/!svn/ver/7957/xmlrpcplugin/trunk/tracrpc/json_rpc.py
23+END
24+util.py
25+K 25
26+svn:wc:ra_dav:version-url
27+V 53
28+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/util.py
29+END
30+__init__.py
31+K 25
32+svn:wc:ra_dav:version-url
33+V 57
34+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/__init__.py
35+END
36+ticket.py
37+K 25
38+svn:wc:ra_dav:version-url
39+V 55
40+/svn/!svn/ver/9912/xmlrpcplugin/trunk/tracrpc/ticket.py
41+END
42+wiki.py
43+K 25
44+svn:wc:ra_dav:version-url
45+V 53
46+/svn/!svn/ver/9818/xmlrpcplugin/trunk/tracrpc/wiki.py
47+END
48+xml_rpc.py
49+K 25
50+svn:wc:ra_dav:version-url
51+V 56
52+/svn/!svn/ver/9360/xmlrpcplugin/trunk/tracrpc/xml_rpc.py
53+END
54+search.py
55+K 25
56+svn:wc:ra_dav:version-url
57+V 55
58+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/search.py
59+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/entries
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/entries Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,343 @@
1+10
2+
3+dir
4+10247
5+http://trac-hacks.org/svn/xmlrpcplugin/trunk/tracrpc
6+http://trac-hacks.org/svn
7+
8+
9+
10+2011-03-18T10:32:52.709335Z
11+9970
12+osimons
13+
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+7322e99d-02ea-0310-aa39-e9a107903beb
28+
29+api.py
30+file
31+
32+
33+
34+
35+2010-05-02T23:49:30.648005Z
36+da02cc0ce0793a6350390f079840f2df
37+2010-05-02T23:49:30.648005Z
38+7916
39+osimons
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+
55+
56+
57+
58+
59+
60+
61+13540
62+
63+web_ui.py
64+file
65+
66+
67+
68+
69+2011-03-18T10:32:52.709335Z
70+d0c5da523bf195ae010b60680208acf9
71+2011-03-18T10:32:52.709335Z
72+9970
73+osimons
74+
75+
76+
77+
78+
79+
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
93+
94+
95+8858
96+
97+tests
98+dir
99+
100+htdocs
101+dir
102+
103+json_rpc.py
104+file
105+
106+
107+
108+
109+2010-05-17T21:11:27.656010Z
110+86202cbe15da2a8766ebf3f780659802
111+2010-05-17T21:11:27.656010Z
112+7957
113+osimons
114+
115+
116+
117+
118+
119+
120+
121+
122+
123+
124+
125+
126+
127+
128+
129+
130+
131+
132+
133+
134+
135+10004
136+
137+util.py
138+file
139+
140+
141+
142+
143+2010-05-02T23:49:30.648005Z
144+e33ca7ad8238685298c9383e33702e8a
145+2010-05-02T23:49:30.648005Z
146+7916
147+osimons
148+
149+
150+
151+
152+
153+
154+
155+
156+
157+
158+
159+
160+
161+
162+
163+
164+
165+
166+
167+
168+
169+1837
170+
171+__init__.py
172+file
173+
174+
175+
176+
177+2010-05-02T23:49:30.648005Z
178+3526fb2c156743b0fde09f8034dc2c25
179+2010-05-02T23:49:30.648005Z
180+7916
181+osimons
182+
183+
184+
185+
186+
187+
188+
189+
190+
191+
192+
193+
194+
195+
196+
197+
198+
199+
200+
201+
202+
203+665
204+
205+ticket.py
206+file
207+
208+
209+
210+
211+2011-03-02T03:40:36.325896Z
212+6449c7c5ee2a0c957593fae79cef15da
213+2011-03-02T03:40:36.325896Z
214+9912
215+osimons
216+
217+
218+
219+
220+
221+
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+
232+
233+
234+
235+
236+
237+20985
238+
239+wiki.py
240+file
241+
242+
243+
244+
245+2011-02-04T00:18:16.742902Z
246+3d143d6f2bbe9f57d765e5cacf38d6f0
247+2011-02-04T00:18:16.742902Z
248+9818
249+osimons
250+
251+
252+
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
263+
264+
265+
266+
267+
268+
269+
270+
271+8836
272+
273+xml_rpc.py
274+file
275+
276+
277+
278+
279+2010-10-27T22:10:47.312851Z
280+fc0ba6c11ad9a1400bde9f5d3f2ae240
281+2010-10-27T22:10:47.312851Z
282+9360
283+osimons
284+
285+
286+
287+
288+
289+
290+
291+
292+
293+
294+
295+
296+
297+
298+
299+
300+
301+
302+
303+
304+
305+7596
306+
307+search.py
308+file
309+
310+
311+
312+
313+2010-05-02T23:49:30.648005Z
314+11f818f69a8a474437b854a392d00d76
315+2010-05-02T23:49:30.648005Z
316+7916
317+osimons
318+
319+
320+
321+
322+
323+
324+
325+
326+
327+
328+
329+
330+
331+
332+
333+
334+
335+
336+
337+
338+
339+2207
340+
341+templates
342+dir
343+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/__init__.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/__init__.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,24 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+from tracrpc.api import *
10+from tracrpc.json_rpc import *
11+from tracrpc.xml_rpc import *
12+from tracrpc.web_ui import *
13+from tracrpc.ticket import *
14+from tracrpc.wiki import *
15+from tracrpc.search import *
16+
17+__author__ = ['Alec Thomas <alec@swapoff.org>',
18+ 'Odd Simon Simonsen <simon-code@bvnetwork.no>']
19+__license__ = 'BSD'
20+
21+try:
22+ __version__ = __import__('pkg_resources').get_distribution('TracXMLRPC').version
23+except (ImportError, pkg_resources.DistributionNotFound):
24+ pass
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/api.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/api.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,339 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import inspect
10+import types
11+from datetime import datetime
12+import xmlrpclib
13+
14+from trac.core import *
15+from trac.perm import IPermissionRequestor
16+
17+__all__ = ['expose_rpc', 'IRPCProtocol', 'IXMLRPCHandler', 'AbstractRPCHandler',
18+ 'Method', 'XMLRPCSystem', 'Binary', 'RPCError', 'MethodNotFound',
19+ 'ProtocolException', 'ServiceException']
20+
21+class Binary(xmlrpclib.Binary):
22+ """ RPC Binary type. Currently == xmlrpclib.Binary. """
23+ pass
24+
25+#----------------------------------------------------------------
26+# RPC Exception classes
27+#----------------------------------------------------------------
28+class RPCError(TracError):
29+ """ Error class for general RPC-related errors. """
30+
31+class MethodNotFound(RPCError):
32+ """ Error to raise when requested method is not found. """
33+
34+class _CompositeRpcError(RPCError):
35+ def __init__(self, details, title=None, show_traceback=False):
36+ if isinstance(details, Exception):
37+ self._exc = details
38+ message = unicode(details)
39+ else :
40+ self._exc = None
41+ message = details
42+ RPCError.__init__(self, message, title, show_traceback)
43+ def __unicode__(self):
44+ return u"%s details : %s" % (self.__class__.__name__, self.message)
45+
46+class ProtocolException(_CompositeRpcError):
47+ """Protocol could not handle RPC request. Usually this means
48+ that the request has some sort of syntactic error, a library
49+ needed to parse the RPC call is not available, or similar errors."""
50+
51+class ServiceException(_CompositeRpcError):
52+ """The called method threw an exception. Helpful to identify bugs ;o)"""
53+
54+RPC_TYPES = {int: 'int', bool: 'boolean', str: 'string', float: 'double',
55+ datetime: 'dateTime.iso8601', Binary: 'base64',
56+ list: 'array', dict: 'struct', None : 'int'}
57+
58+def expose_rpc(permission, return_type, *arg_types):
59+ """ Decorator for exposing a method as an RPC call with the given
60+ signature. """
61+ def decorator(func):
62+ if not hasattr(func, '_xmlrpc_signatures'):
63+ func._xmlrpc_signatures = []
64+ func._xml_rpc_permission = permission
65+ func._xmlrpc_signatures.append((return_type,) + tuple(arg_types))
66+ return func
67+ return decorator
68+
69+
70+class IRPCProtocol(Interface):
71+
72+ def rpc_info():
73+ """ Returns a tuple of (name, docs). Method provides
74+ general information about the protocol used for the RPC HTML view.
75+
76+ name: Shortname like 'XML-RPC'.
77+ docs: Documentation for the protocol.
78+ """
79+
80+ def rpc_match():
81+ """ Return an iterable of (path_item, content_type) combinations that
82+ will be handled by the protocol.
83+
84+ path_item: Single word to use for matching against
85+ (/login)?/<path_item>. Answer to 'rpc' only if possible.
86+ content_type: Starts-with check of 'Content-Type' request header. """
87+
88+ def parse_rpc_request(req, content_type):
89+ """ Parse RPC requests.
90+
91+ req : HTTP request object
92+ content_type : Input MIME type
93+
94+ Return a dictionary with the following keys set. All the other
95+ values included in this mapping will be ignored by the core
96+ RPC subsystem, will be protocol-specific, and SHOULD NOT be
97+ needed in order to invoke a given method.
98+
99+ method (MANDATORY): target method name (e.g. 'ticket.get')
100+ params (OPTIONAL) : a tuple containing input positional arguments
101+ headers (OPTIONAL) : if the protocol supports custom headers set
102+ by the client, then this value SHOULD be a
103+ dictionary binding `header name` to `value`.
104+ However, protocol handlers as well as target
105+ RPC methods *MUST (SHOULD ?) NOT* rely on
106+ specific values assigned to a particular
107+ header in order to send a response back
108+ to the client.
109+ mimetype : request MIME-type. This value will be set
110+ by core RPC components after calling
111+ this method so, please, ignore
112+
113+ If the request cannot be parsed this method *MUST* raise
114+ an instance of `ProtocolException` optionally wrapping another
115+ exception containing details about the failure.
116+ """
117+
118+ def send_rpc_result(req, result):
119+ """Serialize the result of the RPC call and send it back to
120+ the client.
121+
122+ req : Request object. The same mapping returned by
123+ `parse_rpc_request` can be accessed through
124+ `req.rpc` (see above).
125+ result : The value returned by the target RPC method
126+ """
127+
128+ def send_rpc_error(req, rpcreq, e):
129+ """Send a fault message back to the caller. Exception type
130+ and message are used for this purpose. This method *SHOULD*
131+ handle `RPCError`, `PermissionError`, `ResourceNotFound` and
132+ their subclasses. This method is *ALWAYS* called from within
133+ an exception handler.
134+
135+ req : Request object. The same mapping returned by
136+ `parse_rpc_request` can be accessed through
137+ `req.rpc` (see above).
138+ e : exception object describing the failure
139+ """
140+
141+class IXMLRPCHandler(Interface):
142+
143+ def xmlrpc_namespace():
144+ """ Provide the namespace in which a set of methods lives.
145+ This can be overridden if the 'name' element is provided by
146+ xmlrpc_methods(). """
147+
148+ def xmlrpc_methods():
149+ """ Return an iterator of (permission, signatures, callable[, name]),
150+ where callable is exposed via XML-RPC if the authenticated user has the
151+ appropriate permission.
152+
153+ The callable itself can be a method or a normal method. The first
154+ argument passed will always be a request object. The XMLRPCSystem
155+ performs some extra magic to remove the "self" and "req" arguments when
156+ listing the available methods.
157+
158+ Signatures is a list of XML-RPC introspection signatures for this
159+ method. Each signature is a tuple consisting of the return type
160+ followed by argument types.
161+ """
162+
163+class AbstractRPCHandler(Component):
164+ implements(IXMLRPCHandler)
165+ abstract = True
166+
167+ def _init_methods(self):
168+ self._rpc_methods = []
169+ for name, val in inspect.getmembers(self):
170+ if hasattr(val, '_xmlrpc_signatures'):
171+ self._rpc_methods.append((val._xml_rpc_permission, val._xmlrpc_signatures, val, name))
172+
173+ def xmlrpc_methods(self):
174+ if not hasattr(self, '_rpc_methods'):
175+ self._init_methods()
176+ return self._rpc_methods
177+
178+
179+class Method(object):
180+ """ Represents an XML-RPC exposed method. """
181+ def __init__(self, provider, permission, signatures, callable, name = None):
182+ """ Accept a signature in the form returned by xmlrpc_methods. """
183+ self.permission = permission
184+ self.callable = callable
185+ self.rpc_signatures = signatures
186+ self.description = inspect.getdoc(callable)
187+ if name is None:
188+ self.name = provider.xmlrpc_namespace() + '.' + callable.__name__
189+ else:
190+ self.name = provider.xmlrpc_namespace() + '.' + name
191+ self.namespace = provider.xmlrpc_namespace()
192+ self.namespace_description = inspect.getdoc(provider)
193+
194+ def __call__(self, req, args):
195+ if self.permission:
196+ req.perm.assert_permission(self.permission)
197+ result = self.callable(req, *args)
198+ # If result is null, return a zero
199+ if result is None:
200+ result = 0
201+ elif isinstance(result, dict):
202+ pass
203+ elif not isinstance(result, basestring):
204+ # Try and convert result to a list
205+ try:
206+ result = [i for i in result]
207+ except TypeError:
208+ pass
209+ return (result,)
210+
211+ def _get_signature(self):
212+ """ Return the signature of this method. """
213+ if hasattr(self, '_signature'):
214+ return self._signature
215+ fullargspec = inspect.getargspec(self.callable)
216+ argspec = fullargspec[0]
217+ assert argspec[0:2] == ['self', 'req'] or argspec[0] == 'req', \
218+ 'Invalid argspec %s for %s' % (argspec, self.name)
219+ while argspec and (argspec[0] in ('self', 'req')):
220+ argspec.pop(0)
221+ argspec.reverse()
222+ defaults = fullargspec[3]
223+ if not defaults:
224+ defaults = []
225+ else:
226+ defaults = list(defaults)
227+ args = []
228+ sig = []
229+ for sigcand in self.xmlrpc_signatures():
230+ if len(sig) < len(sigcand):
231+ sig = sigcand
232+ sig = list(sig)
233+ for arg in argspec:
234+ if defaults:
235+ value = defaults.pop()
236+ if type(value) is str:
237+ if '"' in value:
238+ value = "'%s'" % value
239+ else:
240+ value = '"%s"' % value
241+ arg += '=%s' % value
242+ args.insert(0, RPC_TYPES[sig.pop()] + ' ' + arg)
243+ self._signature = '%s %s(%s)' % (RPC_TYPES[sig.pop()], self.name, ', '.join(args))
244+ return self._signature
245+
246+ signature = property(_get_signature)
247+
248+ def xmlrpc_signatures(self):
249+ """ Signature as an XML-RPC 'signature'. """
250+ return self.rpc_signatures
251+
252+
253+class XMLRPCSystem(Component):
254+ """ Core of the RPC system. """
255+ implements(IPermissionRequestor, IXMLRPCHandler)
256+
257+ method_handlers = ExtensionPoint(IXMLRPCHandler)
258+
259+ def __init__(self):
260+ self.env.systeminfo.append(('RPC',
261+ __import__('tracrpc', ['__version__']).__version__))
262+
263+ # IPermissionRequestor methods
264+ def get_permission_actions(self):
265+ yield 'XML_RPC'
266+
267+ # IXMLRPCHandler methods
268+ def xmlrpc_namespace(self):
269+ return 'system'
270+
271+ def xmlrpc_methods(self):
272+ yield ('XML_RPC', ((list, list),), self.multicall)
273+ yield ('XML_RPC', ((list,),), self.listMethods)
274+ yield ('XML_RPC', ((str, str),), self.methodHelp)
275+ yield ('XML_RPC', ((list, str),), self.methodSignature)
276+ yield ('XML_RPC', ((list,),), self.getAPIVersion)
277+
278+ def get_method(self, method):
279+ """ Get an RPC signature by full name. """
280+ for provider in self.method_handlers:
281+ for candidate in provider.xmlrpc_methods():
282+ #self.env.log.debug(candidate)
283+ p = Method(provider, *candidate)
284+ if p.name == method:
285+ return p
286+ raise MethodNotFound('RPC method "%s" not found' % method)
287+
288+ # Exported methods
289+ def all_methods(self, req):
290+ """ List all methods exposed via RPC. Returns a list of Method objects. """
291+ for provider in self.method_handlers:
292+ for candidate in provider.xmlrpc_methods():
293+ # Expand all fields of method description
294+ yield Method(provider, *candidate)
295+
296+ def multicall(self, req, signatures):
297+ """ Takes an array of RPC calls encoded as structs of the form (in
298+ a Pythonish notation here): `{'methodName': string, 'params': array}`.
299+ For JSON-RPC multicall, signatures is an array of regular method call
300+ structs, and result is an array of return structures.
301+ """
302+ for signature in signatures:
303+ try:
304+ yield self.get_method(signature['methodName'])(req, signature['params'])
305+ except Exception, e:
306+ yield e
307+
308+ def listMethods(self, req):
309+ """ This method returns a list of strings, one for each (non-system)
310+ method supported by the RPC server. """
311+ for method in self.all_methods(req):
312+ yield method.name
313+
314+ def methodHelp(self, req, method):
315+ """ This method takes one parameter, the name of a method implemented
316+ by the RPC server. It returns a documentation string describing the
317+ use of that method. If no such string is available, an empty string is
318+ returned. The documentation string may contain HTML markup. """
319+ p = self.get_method(method)
320+ return '\n'.join((p.signature, '', p.description))
321+
322+ def methodSignature(self, req, method):
323+ """ This method takes one parameter, the name of a method implemented
324+ by the RPC server.
325+
326+ It returns an array of possible signatures for this method. A signature
327+ is an array of types. The first of these types is the return type of
328+ the method, the rest are parameters. """
329+ p = self.get_method(method)
330+ return [','.join([RPC_TYPES[x] for x in sig]) for sig in p.xmlrpc_signatures()]
331+
332+ def getAPIVersion(self, req):
333+ """ Returns a list with three elements. First element is the
334+ epoch (0=Trac 0.10, 1=Trac 0.11 or higher). Second element is the major
335+ version number, third is the minor. Changes to the major version
336+ indicate API breaking changes, while minor version changes are simple
337+ additions, bug fixes, etc. """
338+ import tracrpc
339+ return map(int, tracrpc.__version__.split('-')[0].split('.'))
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/json_rpc.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/json_rpc.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,235 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import datetime
9+from itertools import izip
10+import re
11+from types import GeneratorType
12+
13+from trac.core import *
14+from trac.perm import PermissionError
15+from trac.resource import ResourceNotFound
16+from trac.util.datefmt import utc
17+from trac.util.text import to_unicode
18+from trac.web.api import RequestDone
19+
20+from tracrpc.api import IRPCProtocol, XMLRPCSystem, Binary, \
21+ RPCError, MethodNotFound, ProtocolException
22+from tracrpc.util import exception_to_unicode, empty, prepare_docs
23+
24+__all__ = ['JsonRpcProtocol']
25+
26+try:
27+ import json
28+ if not (hasattr(json, 'JSONEncoder') \
29+ and hasattr(json, 'JSONDecoder')):
30+ raise AttributeError("Incorrect JSON library found.")
31+except (ImportError, AttributeError):
32+ try:
33+ import simplejson as json
34+ except ImportError:
35+ json = None
36+ __all__ = []
37+
38+if json:
39+ class TracRpcJSONEncoder(json.JSONEncoder):
40+ """ Extending the JSON encoder to support some additional types:
41+ 1. datetime.datetime => {'__jsonclass__': ["datetime", "<rfc3339str>"]}
42+ 2. tracrpc.api.Binary => {'__jsonclass__': ["binary", "<base64str>"]}
43+ 3. empty => '' """
44+
45+ def default(self, obj):
46+ if isinstance(obj, datetime.datetime):
47+ # http://www.ietf.org/rfc/rfc3339.txt
48+ return {'__jsonclass__': ["datetime",
49+ obj.strftime('%Y-%m-%dT%H:%M:%S')]}
50+ elif isinstance(obj, Binary):
51+ return {'__jsonclass__': ["binary",
52+ obj.data.encode("base64")]}
53+ elif obj is empty:
54+ return ''
55+ else:
56+ return json.JSONEncoder(self, obj)
57+
58+ class TracRpcJSONDecoder(json.JSONDecoder):
59+ """ Extending the JSON decoder to support some additional types:
60+ 1. {'__jsonclass__': ["datetime", "<rfc3339str>"]} => datetime.datetime
61+ 2. {'__jsonclass__': ["binary", "<base64str>"]} => tracrpc.api.Binary """
62+
63+ dt = re.compile(
64+ '^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,}))?')
65+
66+ def _normalize(self, obj):
67+ """ Helper to traverse JSON decoded object for custom types. """
68+ if isinstance(obj, tuple):
69+ return tuple(self._normalize(item) for item in obj)
70+ elif isinstance(obj, list):
71+ return [self._normalize(item) for item in obj]
72+ elif isinstance(obj, dict):
73+ if obj.keys() == ['__jsonclass__']:
74+ kind, val = obj['__jsonclass__']
75+ if kind == 'datetime':
76+ dt = self.dt.match(val)
77+ if not dt:
78+ raise Exception(
79+ "Invalid datetime string (%s)" % val)
80+ dt = tuple([int(i) for i in dt.groups() if i])
81+ kw_args = {'tzinfo': utc}
82+ return datetime.datetime(*dt, **kw_args)
83+ elif kind == 'binary':
84+ try:
85+ bin = val.decode("base64")
86+ return Binary(bin)
87+ except:
88+ raise Exception("Invalid base64 string")
89+ else:
90+ raise Exception("Unknown __jsonclass__: %s" % kind)
91+ else:
92+ return dict(self._normalize(obj.items()))
93+ elif isinstance(obj, basestring):
94+ return to_unicode(obj)
95+ else:
96+ return obj
97+
98+ def decode(self, obj, *args, **kwargs):
99+ obj = json.JSONDecoder.decode(self, obj, *args, **kwargs)
100+ return self._normalize(obj)
101+
102+ class JsonProtocolException(ProtocolException):
103+ """Impossible to handle JSON-RPC request."""
104+ def __init__(self, details, code=-32603, title=None, show_traceback=False):
105+ ProtocolException.__init__(self, details, title, show_traceback)
106+ self.code = code
107+
108+ class JsonRpcProtocol(Component):
109+ r"""
110+ Example `POST` request using `curl` with `Content-Type` header
111+ and body:
112+
113+ {{{
114+ user: ~ > cat body.json
115+ {"params": ["WikiStart"], "method": "wiki.getPage", "id": 123}
116+ user: ~ > curl -H "Content-Type: application/json" --data @body.json ${req.abs_href.rpc()}
117+ {"id": 123, "error": null, "result": "= Welcome to....
118+ }}}
119+
120+ Implementation details:
121+
122+ * JSON-RPC has no formalized type system, so a class-hint system is used
123+ for input and output of non-standard types:
124+ * `{"__jsonclass__": ["datetime", "YYYY-MM-DDTHH:MM:SS"]} => DateTime (UTC)`
125+ * `{"__jsonclass__": ["binary", "<base64-encoded>"]} => Binary`
126+ * `"id"` is optional, and any marker value received with a
127+ request is returned with the response.
128+ """
129+
130+ implements(IRPCProtocol)
131+
132+ # IRPCProtocol methods
133+
134+ def rpc_info(self):
135+ return ('JSON-RPC', prepare_docs(self.__doc__))
136+
137+ def rpc_match(self):
138+ yield('rpc', 'application/json')
139+ # Legacy path - provided for backwards compatibility:
140+ yield ('jsonrpc', 'application/json')
141+
142+ def parse_rpc_request(self, req, content_type):
143+ """ Parse JSON-RPC requests"""
144+ if not json:
145+ self.log.debug("RPC(json) call ignored (not available).")
146+ raise JsonProtocolException("Error: JSON-RPC not available.\n")
147+ try:
148+ data = json.load(req, cls=TracRpcJSONDecoder)
149+ self.log.info("RPC(json) JSON-RPC request ID : %s.", data.get('id'))
150+ if data.get('method') == 'system.multicall':
151+ # Prepare for multicall
152+ self.log.debug("RPC(json) Multicall request %s", data)
153+ params = data.get('params', [])
154+ for signature in params :
155+ signature['methodName'] = signature.get('method', '')
156+ data['params'] = [params]
157+ return data
158+ except Exception, e:
159+ # Abort with exception - no data can be read
160+ self.log.error("RPC(json) decode error %s",
161+ exception_to_unicode(e, traceback=True))
162+ raise JsonProtocolException(e, -32700)
163+
164+ def send_rpc_result(self, req, result):
165+ """Send JSON-RPC response back to the caller."""
166+ rpcreq = req.rpc
167+ r_id = rpcreq.get('id')
168+ try:
169+ if rpcreq.get('method') == 'system.multicall':
170+ # Custom multicall
171+ args = (rpcreq.get('params') or [[]])[0]
172+ mcresults = [self._json_result(
173+ isinstance(value, Exception) and \
174+ value or value[0], \
175+ sig.get('id') or r_id) \
176+ for sig, value in izip(args, result)]
177+
178+ response = self._json_result(mcresults, r_id)
179+ else:
180+ response = self._json_result(result, r_id)
181+ try: # JSON encoding
182+ self.log.debug("RPC(json) result: %s" % repr(response))
183+ response = json.dumps(response, cls=TracRpcJSONEncoder)
184+ except Exception, e:
185+ response = json.dumps(self._json_error(e, r_id=r_id),
186+ cls=TracRpcJSONEncoder)
187+ except Exception, e:
188+ self.log.error("RPC(json) error %s" % exception_to_unicode(e,
189+ traceback=True))
190+ response = json.dumps(self._json_error(e, r_id=r_id),
191+ cls=TracRpcJSONEncoder)
192+ self._send_response(req, response + '\n', rpcreq['mimetype'])
193+
194+ def send_rpc_error(self, req, e):
195+ """Send a JSON-RPC fault message back to the caller. """
196+ rpcreq = req.rpc
197+ r_id = rpcreq.get('id')
198+ response = json.dumps(self._json_error(e, r_id=r_id), \
199+ cls=TracRpcJSONEncoder)
200+ self._send_response(req, response + '\n', rpcreq['mimetype'])
201+
202+ # Internal methods
203+
204+ def _send_response(self, req, response, content_type='application/json'):
205+ self.log.debug("RPC(json) encoded response: %s" % response)
206+ response = to_unicode(response).encode("utf-8")
207+ req.send_response(200)
208+ req.send_header('Content-Type', content_type)
209+ req.send_header('Content-Length', len(response))
210+ req.end_headers()
211+ req.write(response)
212+ raise RequestDone()
213+
214+ def _json_result(self, result, r_id=None):
215+ """ Create JSON-RPC response dictionary. """
216+ if not isinstance(result, Exception):
217+ return {'result': result, 'error': None, 'id': r_id}
218+ else :
219+ return self._json_error(result, r_id=r_id)
220+
221+ def _json_error(self, e, c=None, r_id=None):
222+ """ Makes a response dictionary that is an error. """
223+ if isinstance(e, MethodNotFound):
224+ c = -32601
225+ elif isinstance(e, PermissionError):
226+ c = 403
227+ elif isinstance(e, ResourceNotFound):
228+ c = 404
229+ else:
230+ c = c or hasattr(e, 'code') and e.code or -32603
231+ return {'result': None, 'id': r_id, 'error': {
232+ 'name': hasattr(e, 'name') and e.name or 'JSONRPCError',
233+ 'code': c,
234+ 'message': to_unicode(e)}}
235+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/search.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/search.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,63 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+from trac.core import *
10+from trac.search.api import ISearchSource
11+from trac.search.web_ui import SearchModule
12+from trac.util.compat import set
13+
14+from tracrpc.api import IXMLRPCHandler
15+
16+__all__ = ['SearchRPC']
17+
18+class SearchRPC(Component):
19+ """ Search Trac. """
20+ implements(IXMLRPCHandler)
21+
22+ search_sources = ExtensionPoint(ISearchSource)
23+
24+ # IXMLRPCHandler methods
25+ def xmlrpc_namespace(self):
26+ return 'search'
27+
28+ def xmlrpc_methods(self):
29+ yield ('SEARCH_VIEW', ((list,),), self.getSearchFilters)
30+ yield ('SEARCH_VIEW', ((list, str), (list, str, list)), self.performSearch)
31+
32+ # Others
33+ def getSearchFilters(self, req):
34+ """ Retrieve a list of search filters with each element in the form
35+ (name, description). """
36+ for source in self.search_sources:
37+ for filter in source.get_search_filters(req):
38+ yield filter
39+
40+ def performSearch(self, req, query, filters=None):
41+ """ Perform a search using the given filters. Defaults to all if not
42+ provided. Results are returned as a list of tuples in the form
43+ (href, title, date, author, excerpt)."""
44+ query = SearchModule(self.env)._get_search_terms(query)
45+ filters_provided = filters is not None
46+ chosen_filters = set(filters or [])
47+ available_filters = []
48+ for source in self.search_sources:
49+ available_filters += source.get_search_filters(req)
50+
51+ filters = [f[0] for f in available_filters if f[0] in chosen_filters]
52+ if not filters:
53+ if filters_provided:
54+ return []
55+ filters = [f[0] for f in available_filters]
56+ self.env.log.debug("Searching with %s" % filters)
57+
58+ results = []
59+ for source in self.search_sources:
60+ for result in source.get_search_results(req, query, filters):
61+ results.append(['/'.join(req.base_url.split('/')[0:3])
62+ + result[0]] + list(result[1:]))
63+ return results
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/ticket.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/ticket.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,488 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import inspect
10+from datetime import datetime
11+
12+import genshi
13+
14+from trac.attachment import Attachment
15+from trac.core import *
16+from trac.perm import PermissionError
17+from trac.resource import Resource, ResourceNotFound
18+import trac.ticket.model as model
19+import trac.ticket.query as query
20+from trac.ticket.api import TicketSystem
21+from trac.ticket.notification import TicketNotifyEmail
22+from trac.ticket.web_ui import TicketModule
23+from trac.web.chrome import add_warning
24+from trac.util.datefmt import to_datetime, utc
25+
26+from tracrpc.api import IXMLRPCHandler, expose_rpc, Binary
27+from tracrpc.util import StringIO, to_utimestamp
28+
29+__all__ = ['TicketRPC']
30+
31+class TicketRPC(Component):
32+ """ An interface to Trac's ticketing system. """
33+
34+ implements(IXMLRPCHandler)
35+
36+ # IXMLRPCHandler methods
37+ def xmlrpc_namespace(self):
38+ return 'ticket'
39+
40+ def xmlrpc_methods(self):
41+ yield (None, ((list,), (list, str)), self.query)
42+ yield (None, ((list, datetime),), self.getRecentChanges)
43+ yield (None, ((list, int),), self.getAvailableActions)
44+ yield (None, ((list, int),), self.getActions)
45+ yield (None, ((list, int),), self.get)
46+ yield ('TICKET_CREATE', ((int, str, str),
47+ (int, str, str, dict),
48+ (int, str, str, dict, bool),
49+ (int, str, str, dict, bool, datetime)),
50+ self.create)
51+ yield (None, ((list, int, str),
52+ (list, int, str, dict),
53+ (list, int, str, dict, bool),
54+ (list, int, str, dict, bool, str),
55+ (list, int, str, dict, bool, str, datetime)),
56+ self.update)
57+ yield (None, ((None, int),), self.delete)
58+ yield (None, ((dict, int), (dict, int, int)), self.changeLog)
59+ yield (None, ((list, int),), self.listAttachments)
60+ yield (None, ((Binary, int, str),), self.getAttachment)
61+ yield (None,
62+ ((str, int, str, str, Binary, bool),
63+ (str, int, str, str, Binary)),
64+ self.putAttachment)
65+ yield (None, ((bool, int, str),), self.deleteAttachment)
66+ yield ('TICKET_VIEW', ((list,),), self.getTicketFields)
67+
68+ # Exported methods
69+ def query(self, req, qstr='status!=closed'):
70+ """
71+ Perform a ticket query, returning a list of ticket ID's.
72+ All queries will use stored settings for maximum number of results per
73+ page and paging options. Use `max=n` to define number of results to
74+ receive, and use `page=n` to page through larger result sets. Using
75+ `max=0` will turn off paging and return all results.
76+ """
77+ q = query.Query.from_string(self.env, qstr)
78+ ticket_realm = Resource('ticket')
79+ out = []
80+ for t in q.execute(req):
81+ tid = t['id']
82+ if 'TICKET_VIEW' in req.perm(ticket_realm(id=tid)):
83+ out.append(tid)
84+ return out
85+
86+ def getRecentChanges(self, req, since):
87+ """Returns a list of IDs of tickets that have changed since timestamp."""
88+ since = to_utimestamp(since)
89+ db = self.env.get_db_cnx()
90+ cursor = db.cursor()
91+ cursor.execute('SELECT id FROM ticket'
92+ ' WHERE changetime >= %s', (since,))
93+ result = []
94+ ticket_realm = Resource('ticket')
95+ for row in cursor:
96+ tid = int(row[0])
97+ if 'TICKET_VIEW' in req.perm(ticket_realm(id=tid)):
98+ result.append(tid)
99+ return result
100+
101+ def getAvailableActions(self, req, id):
102+ """ Deprecated - will be removed. Replaced by `getActions()`. """
103+ self.log.warning("Rpc ticket.getAvailableActions is deprecated")
104+ return [action[0] for action in self.getActions(req, id)]
105+
106+ def getActions(self, req, id):
107+ """Returns the actions that can be performed on the ticket as a list of
108+ `[action, label, hints, [input_fields]]` elements, where `input_fields` is
109+ a list of `[name, value, [options]]` for any required action inputs."""
110+ ts = TicketSystem(self.env)
111+ t = model.Ticket(self.env, id)
112+ actions = []
113+ for action in ts.get_available_actions(req, t):
114+ fragment = genshi.builder.Fragment()
115+ hints = []
116+ first_label = None
117+ for controller in ts.action_controllers:
118+ if action in [c_action for c_weight, c_action \
119+ in controller.get_ticket_actions(req, t)]:
120+ label, widget, hint = \
121+ controller.render_ticket_action_control(req, t, action)
122+ fragment += widget
123+ hints.append(hint)
124+ first_label = first_label == None and label or first_label
125+ controls = []
126+ for elem in fragment.children:
127+ if not isinstance(elem, genshi.builder.Element):
128+ continue
129+ if elem.tag == 'input':
130+ controls.append((elem.attrib.get('name'),
131+ elem.attrib.get('value'), []))
132+ elif elem.tag == 'select':
133+ value = ''
134+ options = []
135+ for opt in elem.children:
136+ if not (opt.tag == 'option' and opt.children):
137+ continue
138+ option = opt.children[0]
139+ options.append(option)
140+ if opt.attrib.get('selected'):
141+ value = option
142+ controls.append((elem.attrib.get('name'),
143+ value, options))
144+ actions.append((action, first_label, ". ".join(hints) + '.', controls))
145+ return actions
146+
147+ def get(self, req, id):
148+ """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
149+ t = model.Ticket(self.env, id)
150+ req.perm(t.resource).require('TICKET_VIEW')
151+ t['_ts'] = str(t.time_changed)
152+ return (t.id, t.time_created, t.time_changed, t.values)
153+
154+ def create(self, req, summary, description, attributes={}, notify=False, when=None):
155+ """ Create a new ticket, returning the ticket ID.
156+ Overriding 'when' requires admin permission. """
157+ t = model.Ticket(self.env)
158+ t['summary'] = summary
159+ t['description'] = description
160+ t['reporter'] = req.authname
161+ for k, v in attributes.iteritems():
162+ t[k] = v
163+ t['status'] = 'new'
164+ t['resolution'] = ''
165+ # custom create timestamp?
166+ if when and not 'TICKET_ADMIN' in req.perm:
167+ self.log.warn("RPC ticket.create: %r not allowed to create with "
168+ "non-current timestamp (%r)", req.authname, when)
169+ when = None
170+ t.insert(when=when)
171+ # Call ticket change listeners
172+ ts = TicketSystem(self.env)
173+ for listener in ts.change_listeners:
174+ listener.ticket_created(t)
175+ if notify:
176+ try:
177+ tn = TicketNotifyEmail(self.env)
178+ tn.notify(t, newticket=True)
179+ except Exception, e:
180+ self.log.exception("Failure sending notification on creation "
181+ "of ticket #%s: %s" % (t.id, e))
182+ return t.id
183+
184+ def update(self, req, id, comment, attributes={}, notify=False, author='', when=None):
185+ """ Update a ticket, returning the new ticket in the same form as
186+ get(). 'New-style' call requires two additional items in attributes:
187+ (1) 'action' for workflow support (including any supporting fields
188+ as retrieved by getActions()),
189+ (2) '_ts' changetime token for detecting update collisions (as received
190+ from get() or update() calls).
191+ ''Calling update without 'action' and '_ts' changetime token is
192+ deprecated, and will raise errors in a future version.'' """
193+ t = model.Ticket(self.env, id)
194+ # custom author?
195+ if author and not (req.authname == 'anonymous' \
196+ or 'TICKET_ADMIN' in req.perm(t.resource)):
197+ # only allow custom author if anonymous is permitted or user is admin
198+ self.log.warn("RPC ticket.update: %r not allowed to change author "
199+ "to %r for comment on #%d", req.authname, author, id)
200+ author = ''
201+ author = author or req.authname
202+ # custom change timestamp?
203+ if when and not 'TICKET_ADMIN' in req.perm(t.resource):
204+ self.log.warn("RPC ticket.update: %r not allowed to update #%d with "
205+ "non-current timestamp (%r)", author, id, when)
206+ when = None
207+ when = when or to_datetime(None, utc)
208+ # and action...
209+ if not 'action' in attributes:
210+ # FIXME: Old, non-restricted update - remove soon!
211+ self.log.warning("Rpc ticket.update for ticket %d by user %s " \
212+ "has no workflow 'action'." % (id, req.authname))
213+ req.perm(t.resource).require('TICKET_MODIFY')
214+ time_changed = attributes.pop('_ts', None)
215+ if time_changed and str(time_changed) != str(t.time_changed):
216+ raise TracError("Ticket has been updated since last get().")
217+ for k, v in attributes.iteritems():
218+ t[k] = v
219+ t.save_changes(author, comment, when=when)
220+ else:
221+ ts = TicketSystem(self.env)
222+ tm = TicketModule(self.env)
223+ # TODO: Deprecate update without time_changed timestamp
224+ time_changed = str(attributes.pop('_ts', t.time_changed))
225+ action = attributes.get('action')
226+ avail_actions = ts.get_available_actions(req, t)
227+ if not action in avail_actions:
228+ raise TracError("Rpc: Ticket %d by %s " \
229+ "invalid action '%s'" % (id, req.authname, action))
230+ controllers = list(tm._get_action_controllers(req, t, action))
231+ all_fields = [field['name'] for field in ts.get_ticket_fields()]
232+ for k, v in attributes.iteritems():
233+ if k in all_fields and k != 'status':
234+ t[k] = v
235+ # TicketModule reads req.args - need to move things there...
236+ req.args.update(attributes)
237+ req.args['comment'] = comment
238+ req.args['ts'] = time_changed
239+ changes, problems = tm.get_ticket_changes(req, t, action)
240+ for warning in problems:
241+ add_warning(req, "Rpc ticket.update: %s" % warning)
242+ valid = problems and False or tm._validate_ticket(req, t)
243+ if not valid:
244+ raise TracError(
245+ " ".join([warning for warning in req.chrome['warnings']]))
246+ else:
247+ tm._apply_ticket_changes(t, changes)
248+ self.log.debug("Rpc ticket.update save: %s" % repr(t.values))
249+ t.save_changes(author, comment, when=when)
250+ # Apply workflow side-effects
251+ for controller in controllers:
252+ controller.apply_action_side_effects(req, t, action)
253+ # Call ticket change listeners
254+ for listener in ts.change_listeners:
255+ listener.ticket_changed(t, comment, author, t._old)
256+ if notify:
257+ try:
258+ tn = TicketNotifyEmail(self.env)
259+ tn.notify(t, newticket=False, modtime=when)
260+ except Exception, e:
261+ self.log.exception("Failure sending notification on change of "
262+ "ticket #%s: %s" % (t.id, e))
263+ return self.get(req, t.id)
264+
265+ def delete(self, req, id):
266+ """ Delete ticket with the given id. """
267+ t = model.Ticket(self.env, id)
268+ req.perm(t.resource).require('TICKET_ADMIN')
269+ t.delete()
270+ ts = TicketSystem(self.env)
271+ # Call ticket change listeners
272+ for listener in ts.change_listeners:
273+ listener.ticket_deleted(t)
274+
275+ def changeLog(self, req, id, when=0):
276+ t = model.Ticket(self.env, id)
277+ req.perm(t.resource).require('TICKET_VIEW')
278+ for date, author, field, old, new, permanent in t.get_changelog(when):
279+ yield (date, author, field, old, new, permanent)
280+ # Use existing documentation from Ticket model
281+ changeLog.__doc__ = inspect.getdoc(model.Ticket.get_changelog)
282+
283+ def listAttachments(self, req, ticket):
284+ """ Lists attachments for a given ticket. Returns (filename,
285+ description, size, time, author) for each attachment."""
286+ attachments = []
287+ for a in Attachment.select(self.env, 'ticket', ticket):
288+ if 'ATTACHMENT_VIEW' in req.perm(a.resource):
289+ yield (a.filename, a.description, a.size, a.date, a.author)
290+
291+ def getAttachment(self, req, ticket, filename):
292+ """ returns the content of an attachment. """
293+ attachment = Attachment(self.env, 'ticket', ticket, filename)
294+ req.perm(attachment.resource).require('ATTACHMENT_VIEW')
295+ return Binary(attachment.open().read())
296+
297+ def putAttachment(self, req, ticket, filename, description, data, replace=True):
298+ """ Add an attachment, optionally (and defaulting to) overwriting an
299+ existing one. Returns filename."""
300+ if not model.Ticket(self.env, ticket).exists:
301+ raise ResourceNotFound('Ticket "%s" does not exist' % ticket)
302+ if replace:
303+ try:
304+ attachment = Attachment(self.env, 'ticket', ticket, filename)
305+ req.perm(attachment.resource).require('ATTACHMENT_DELETE')
306+ attachment.delete()
307+ except TracError:
308+ pass
309+ attachment = Attachment(self.env, 'ticket', ticket)
310+ req.perm(attachment.resource).require('ATTACHMENT_CREATE')
311+ attachment.author = req.authname
312+ attachment.description = description
313+ attachment.insert(filename, StringIO(data.data), len(data.data))
314+ return attachment.filename
315+
316+ def deleteAttachment(self, req, ticket, filename):
317+ """ Delete an attachment. """
318+ if not model.Ticket(self.env, ticket).exists:
319+ raise ResourceNotFound('Ticket "%s" does not exists' % ticket)
320+ attachment = Attachment(self.env, 'ticket', ticket, filename)
321+ req.perm(attachment.resource).require('ATTACHMENT_DELETE')
322+ attachment.delete()
323+ return True
324+
325+ def getTicketFields(self, req):
326+ """ Return a list of all ticket fields fields. """
327+ return TicketSystem(self.env).get_ticket_fields()
328+
329+class StatusRPC(Component):
330+ """ An interface to Trac ticket status objects.
331+ Note: Status is defined by workflow, and all methods except `getAll()`
332+ are deprecated no-op methods - these will be removed later. """
333+
334+ implements(IXMLRPCHandler)
335+
336+ # IXMLRPCHandler methods
337+ def xmlrpc_namespace(self):
338+ return 'ticket.status'
339+
340+ def xmlrpc_methods(self):
341+ yield ('TICKET_VIEW', ((list,),), self.getAll)
342+ yield ('TICKET_VIEW', ((dict, str),), self.get)
343+ yield ('TICKET_ADMIN', ((None, str,),), self.delete)
344+ yield ('TICKET_ADMIN', ((None, str, dict),), self.create)
345+ yield ('TICKET_ADMIN', ((None, str, dict),), self.update)
346+
347+ def getAll(self, req):
348+ """ Returns all ticket states described by active workflow. """
349+ return TicketSystem(self.env).get_all_status()
350+
351+ def get(self, req, name):
352+ """ Deprecated no-op method. Do not use. """
353+ # FIXME: Remove
354+ return '0'
355+
356+ def delete(self, req, name):
357+ """ Deprecated no-op method. Do not use. """
358+ # FIXME: Remove
359+ return 0
360+
361+ def create(self, req, name, attributes):
362+ """ Deprecated no-op method. Do not use. """
363+ # FIXME: Remove
364+ return 0
365+
366+ def update(self, req, name, attributes):
367+ """ Deprecated no-op method. Do not use. """
368+ # FIXME: Remove
369+ return 0
370+
371+def ticketModelFactory(cls, cls_attributes):
372+ """ Return a class which exports an interface to trac.ticket.model.<cls>. """
373+ class TicketModelImpl(Component):
374+ implements(IXMLRPCHandler)
375+
376+ def xmlrpc_namespace(self):
377+ return 'ticket.' + cls.__name__.lower()
378+
379+ def xmlrpc_methods(self):
380+ yield ('TICKET_VIEW', ((list,),), self.getAll)
381+ yield ('TICKET_VIEW', ((dict, str),), self.get)
382+ yield ('TICKET_ADMIN', ((None, str,),), self.delete)
383+ yield ('TICKET_ADMIN', ((None, str, dict),), self.create)
384+ yield ('TICKET_ADMIN', ((None, str, dict),), self.update)
385+
386+ def getAll(self, req):
387+ for i in cls.select(self.env):
388+ yield i.name
389+ getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower()
390+
391+ def get(self, req, name):
392+ i = cls(self.env, name)
393+ attributes= {}
394+ for k, default in cls_attributes.iteritems():
395+ v = getattr(i, k)
396+ if v is None:
397+ v = default
398+ attributes[k] = v
399+ return attributes
400+ get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
401+
402+ def delete(self, req, name):
403+ cls(self.env, name).delete()
404+ delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
405+
406+ def create(self, req, name, attributes):
407+ i = cls(self.env)
408+ i.name = name
409+ for k, v in attributes.iteritems():
410+ setattr(i, k, v)
411+ i.insert();
412+ create.__doc__ = """ Create a new ticket %s with the given attributes. """ % cls.__name__.lower()
413+
414+ def update(self, req, name, attributes):
415+ self._updateHelper(name, attributes).update()
416+ update.__doc__ = """ Update ticket %s with the given attributes. """ % cls.__name__.lower()
417+
418+ def _updateHelper(self, name, attributes):
419+ i = cls(self.env, name)
420+ for k, v in attributes.iteritems():
421+ setattr(i, k, v)
422+ return i
423+ TicketModelImpl.__doc__ = """ Interface to ticket %s objects. """ % cls.__name__.lower()
424+ TicketModelImpl.__name__ = '%sRPC' % cls.__name__
425+ return TicketModelImpl
426+
427+def ticketEnumFactory(cls):
428+ """ Return a class which exports an interface to one of the Trac ticket abstract enum types. """
429+ class AbstractEnumImpl(Component):
430+ implements(IXMLRPCHandler)
431+
432+ def xmlrpc_namespace(self):
433+ return 'ticket.' + cls.__name__.lower()
434+
435+ def xmlrpc_methods(self):
436+ yield ('TICKET_VIEW', ((list,),), self.getAll)
437+ yield ('TICKET_VIEW', ((str, str),), self.get)
438+ yield ('TICKET_ADMIN', ((None, str,),), self.delete)
439+ yield ('TICKET_ADMIN', ((None, str, str),), self.create)
440+ yield ('TICKET_ADMIN', ((None, str, str),), self.update)
441+
442+ def getAll(self, req):
443+ for i in cls.select(self.env):
444+ yield i.name
445+ getAll.__doc__ = """ Get a list of all ticket %s names. """ % cls.__name__.lower()
446+
447+ def get(self, req, name):
448+ if (cls.__name__ == 'Status'):
449+ i = cls(self.env)
450+ x = name
451+ else:
452+ i = cls(self.env, name)
453+ x = i.value
454+ return x
455+ get.__doc__ = """ Get a ticket %s. """ % cls.__name__.lower()
456+
457+ def delete(self, req, name):
458+ cls(self.env, name).delete()
459+ delete.__doc__ = """ Delete a ticket %s """ % cls.__name__.lower()
460+
461+ def create(self, req, name, value):
462+ i = cls(self.env)
463+ i.name = name
464+ i.value = value
465+ i.insert()
466+ create.__doc__ = """ Create a new ticket %s with the given value. """ % cls.__name__.lower()
467+
468+ def update(self, req, name, value):
469+ self._updateHelper(name, value).update()
470+ update.__doc__ = """ Update ticket %s with the given value. """ % cls.__name__.lower()
471+
472+ def _updateHelper(self, name, value):
473+ i = cls(self.env, name)
474+ i.value = value
475+ return i
476+
477+ AbstractEnumImpl.__doc__ = """ Interface to ticket %s. """ % cls.__name__.lower()
478+ AbstractEnumImpl.__name__ = '%sRPC' % cls.__name__
479+ return AbstractEnumImpl
480+
481+ticketModelFactory(model.Component, {'name': '', 'owner': '', 'description': ''})
482+ticketModelFactory(model.Version, {'name': '', 'time': 0, 'description': ''})
483+ticketModelFactory(model.Milestone, {'name': '', 'due': 0, 'completed': 0, 'description': ''})
484+
485+ticketEnumFactory(model.Type)
486+ticketEnumFactory(model.Resolution)
487+ticketEnumFactory(model.Priority)
488+ticketEnumFactory(model.Severity)
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/util.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/util.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,57 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+from trac.util.compat import any
10+
11+try:
12+ from cStringIO import StringIO
13+except ImportError:
14+ from StringIO import StringIO
15+
16+try:
17+ # Method only available in Trac 0.11.3 or higher
18+ from trac.util.text import exception_to_unicode
19+except ImportError:
20+ def exception_to_unicode(e, traceback=""):
21+ from trac.util.text import to_unicode
22+ message = '%s: %s' % (e.__class__.__name__, to_unicode(e))
23+ if traceback:
24+ from trac.util import get_last_traceback
25+ traceback_only = get_last_traceback().split('\n')[:-2]
26+ message = '\n%s\n%s' % (to_unicode('\n'.join(traceback_only)),
27+ message)
28+ return message
29+
30+try:
31+ # Constant available from Trac 0.12dev r8612
32+ from trac.util.text import empty
33+except ImportError:
34+ empty = None
35+
36+def accepts_mimetype(req, mimetype):
37+ if isinstance(mimetype, basestring):
38+ mimetype = (mimetype,)
39+ accept = req.get_header('Accept')
40+ if accept is None :
41+ # Don't make judgements if no MIME type expected and method is GET
42+ return req.method == 'GET'
43+ else :
44+ accept = accept.split(',')
45+ return any(x.strip().startswith(y) for x in accept for y in mimetype)
46+
47+def prepare_docs(text, indent=4):
48+ r"""Remove leading whitespace"""
49+ return ''.join(l[indent:] for l in text.splitlines(True))
50+
51+try:
52+ # Micro-second support added to 0.12dev r9210
53+ from trac.util.datefmt import to_utimestamp, from_utimestamp
54+except ImportError:
55+ from trac.util.datefmt import to_timestamp, to_datetime, utc
56+ to_utimestamp = to_timestamp
57+ from_utimestamp = lambda x: to_datetime(x, utc)
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/web_ui.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/web_ui.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,208 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import sys
10+from types import GeneratorType
11+
12+from pkg_resources import resource_filename
13+
14+from genshi.builder import tag
15+from genshi.template.base import TemplateSyntaxError, BadDirectiveError
16+from genshi.template.text import TextTemplate
17+
18+from trac.core import *
19+from trac.perm import PermissionError
20+from trac.resource import ResourceNotFound
21+from trac.util.text import to_unicode
22+from trac.util.translation import _
23+from trac.web.api import RequestDone, HTTPUnsupportedMediaType, \
24+ HTTPInternalError
25+from trac.web.main import IRequestHandler
26+from trac.web.chrome import ITemplateProvider, INavigationContributor, \
27+ add_stylesheet, add_script, add_ctxtnav
28+from trac.wiki.formatter import wiki_to_oneliner
29+
30+from tracrpc.api import XMLRPCSystem, IRPCProtocol, ProtocolException, \
31+ RPCError, ServiceException
32+from tracrpc.util import accepts_mimetype
33+
34+__all__ = ['RPCWeb']
35+
36+class RPCWeb(Component):
37+ """ Handle RPC calls from HTTP clients, as well as presenting a list of
38+ methods available to the currently logged in user. Browsing to
39+ <trac>/rpc or <trac>/login/rpc will display this list. """
40+
41+ implements(IRequestHandler, ITemplateProvider, INavigationContributor)
42+
43+ protocols = ExtensionPoint(IRPCProtocol)
44+
45+ # IRequestHandler methods
46+
47+ def match_request(self, req):
48+ """ Look for available protocols serving at requested path and
49+ content-type. """
50+ content_type = req.get_header('Content-Type') or 'text/html'
51+ must_handle_request = req.path_info in ('/rpc', '/login/rpc')
52+ for protocol in self.protocols:
53+ for p_path, p_type in protocol.rpc_match():
54+ if req.path_info in ['/%s' % p_path, '/login/%s' % p_path]:
55+ must_handle_request = True
56+ if content_type.startswith(p_type):
57+ req.args['protocol'] = protocol
58+ return True
59+ # No protocol call, need to handle for docs or error if handled path
60+ return must_handle_request
61+
62+ def process_request(self, req):
63+ protocol = req.args.get('protocol', None)
64+ content_type = req.get_header('Content-Type') or 'text/html'
65+ if protocol:
66+ # Perform the method call
67+ self.log.debug("RPC incoming request of content type '%s' " \
68+ "dispatched to %s" % (content_type, repr(protocol)))
69+ self._rpc_process(req, protocol, content_type)
70+ elif accepts_mimetype(req, 'text/html') \
71+ or content_type.startswith('text/html'):
72+ return self._dump_docs(req)
73+ else:
74+ # Attempt at API call gone wrong. Raise a plain-text 415 error
75+ body = "No protocol matching Content-Type '%s' at path '%s'." % (
76+ content_type, req.path_info)
77+ self.log.error(body)
78+ req.send_error(None, template='', content_type='text/plain',
79+ status=HTTPUnsupportedMediaType.code, env=None, data=body)
80+
81+ # Internal methods
82+
83+ def _dump_docs(self, req):
84+ self.log.debug("Rendering docs")
85+
86+ # Dump RPC documentation
87+ req.perm.require('XML_RPC') # Need at least XML_RPC
88+ namespaces = {}
89+ for method in XMLRPCSystem(self.env).all_methods(req):
90+ namespace = method.namespace.replace('.', '_')
91+ if namespace not in namespaces:
92+ namespaces[namespace] = {
93+ 'description' : wiki_to_oneliner(
94+ method.namespace_description,
95+ self.env, req=req),
96+ 'methods' : [],
97+ 'namespace' : method.namespace,
98+ }
99+ try:
100+ namespaces[namespace]['methods'].append(
101+ (method.signature,
102+ wiki_to_oneliner(
103+ method.description, self.env, req=req),
104+ method.permission))
105+ except Exception, e:
106+ from tracrpc.util import StringIO
107+ import traceback
108+ out = StringIO()
109+ traceback.print_exc(file=out)
110+ raise Exception('%s: %s\n%s' % (method.name,
111+ str(e), out.getvalue()))
112+ add_stylesheet(req, 'common/css/wiki.css')
113+ add_stylesheet(req, 'tracrpc/rpc.css')
114+ add_script(req, 'tracrpc/rpc.js')
115+ return ('rpc.html',
116+ {'rpc': {'functions': namespaces,
117+ 'protocols': [p.rpc_info() + (list(p.rpc_match()),) \
118+ for p in self.protocols],
119+ 'version': __import__('tracrpc', ['__version__']).__version__
120+ },
121+ 'expand_docs': self._expand_docs
122+ },
123+ None)
124+
125+ def _expand_docs(self, docs, ctx):
126+ try :
127+ tmpl = TextTemplate(docs)
128+ return tmpl.generate(**dict(ctx.items())).render()
129+ except (TemplateSyntaxError, BadDirectiveError), exc:
130+ self.log.exception("Syntax error rendering protocol documentation")
131+ return "'''Syntax error:''' [[BR]] %s" % (str(exc),)
132+ except Exception:
133+ self.log.exception("Runtime error rendering protocol documentation")
134+ return "Error rendering protocol documentation. " \
135+ "Contact your '''Trac''' administrator for details"
136+
137+ def _rpc_process(self, req, protocol, content_type):
138+ """Process incoming RPC request and finalize response."""
139+ proto_id = protocol.rpc_info()[0]
140+ rpcreq = req.rpc = {'mimetype': content_type}
141+ try :
142+ self.log.debug("RPC(%s) call by '%s'", proto_id, req.authname)
143+ rpcreq = req.rpc = protocol.parse_rpc_request(req, content_type)
144+ rpcreq['mimetype'] = content_type
145+
146+ # Important ! Check after parsing RPC request to add
147+ # protocol-specific fields in response
148+ # (e.g. JSON-RPC response `id`)
149+ req.perm.require('XML_RPC') # Need at least XML_RPC
150+
151+ method_name = rpcreq.get('method')
152+ if method_name is None :
153+ raise ProtocolException('Missing method name')
154+ args = rpcreq.get('params') or []
155+ self.log.debug("RPC(%s) call by '%s' %s", proto_id, \
156+ req.authname, method_name)
157+ try :
158+ result = (XMLRPCSystem(self.env).get_method(method_name)(req, args))[0]
159+ if isinstance(result, GeneratorType):
160+ result = list(result)
161+ except (RPCError, PermissionError, ResourceNotFound), e:
162+ raise
163+ except Exception:
164+ e, tb = sys.exc_info()[-2:]
165+ raise ServiceException(e), None, tb
166+ else :
167+ protocol.send_rpc_result(req, result)
168+ except RequestDone :
169+ raise
170+ except (RPCError, PermissionError, ResourceNotFound), e:
171+ self.log.exception("RPC(%s) Error", proto_id)
172+ try :
173+ protocol.send_rpc_error(req, e)
174+ except RequestDone :
175+ raise
176+ except Exception, e :
177+ self.log.exception("RPC(%s) Unhandled protocol error", proto_id)
178+ self._send_unknown_error(req, e)
179+ except Exception, e :
180+ self.log.exception("RPC(%s) Unhandled protocol error", proto_id)
181+ self._send_unknown_error(req, e)
182+
183+ def _send_unknown_error(self, req, e):
184+ """Last recourse if protocol cannot handle the RPC request | error"""
185+ method_name = req.rpc and req.rpc.get('method') or '(undefined)'
186+ body = "Unhandled protocol error calling '%s': %s" % (
187+ method_name, to_unicode(e))
188+ req.send_error(None, template='', content_type='text/plain',
189+ env=None, data=body, status=HTTPInternalError.code)
190+
191+ # ITemplateProvider methods
192+
193+ def get_htdocs_dirs(self):
194+ yield ('tracrpc', resource_filename(__name__, 'htdocs'))
195+
196+ def get_templates_dirs(self):
197+ yield resource_filename(__name__, 'templates')
198+
199+ # INavigationContributor methods
200+
201+ def get_active_navigation_item(self, req):
202+ pass
203+
204+ def get_navigation_items(self, req):
205+ if req.perm.has_permission('XML_RPC'):
206+ yield ('metanav', 'rpc',
207+ tag.a('API', href=req.href.rpc(), accesskey=1))
208+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/wiki.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/wiki.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,209 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import os
10+from datetime import datetime
11+
12+from trac.attachment import Attachment
13+from trac.core import *
14+from trac.mimeview import Context
15+from trac.resource import Resource, ResourceNotFound
16+from trac.wiki.api import WikiSystem
17+from trac.wiki.model import WikiPage
18+from trac.wiki.formatter import wiki_to_html, format_to_html
19+
20+from tracrpc.api import IXMLRPCHandler, expose_rpc, Binary
21+from tracrpc.util import StringIO, to_utimestamp, from_utimestamp
22+
23+__all__ = ['WikiRPC']
24+
25+class WikiRPC(Component):
26+ """Superset of the
27+ [http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2 WikiRPC API]. """
28+
29+ implements(IXMLRPCHandler)
30+
31+ def __init__(self):
32+ self.wiki = WikiSystem(self.env)
33+
34+ def xmlrpc_namespace(self):
35+ return 'wiki'
36+
37+ def xmlrpc_methods(self):
38+ yield (None, ((dict, datetime),), self.getRecentChanges)
39+ yield ('WIKI_VIEW', ((int,),), self.getRPCVersionSupported)
40+ yield (None, ((str, str), (str, str, int),), self.getPage)
41+ yield (None, ((str, str, int),), self.getPage, 'getPageVersion')
42+ yield (None, ((str, str), (str, str, int)), self.getPageHTML)
43+ yield (None, ((str, str), (str, str, int)), self.getPageHTML, 'getPageHTMLVersion')
44+ yield (None, ((list,),), self.getAllPages)
45+ yield (None, ((dict, str), (dict, str, int)), self.getPageInfo)
46+ yield (None, ((dict, str, int),), self.getPageInfo, 'getPageInfoVersion')
47+ yield (None, ((bool, str, str, dict),), self.putPage)
48+ yield (None, ((list, str),), self.listAttachments)
49+ yield (None, ((Binary, str),), self.getAttachment)
50+ yield (None, ((bool, str, Binary),), self.putAttachment)
51+ yield (None, ((bool, str, str, str, Binary),
52+ (bool, str, str, str, Binary, bool)),
53+ self.putAttachmentEx)
54+ yield (None, ((bool, str),(bool, str, int)), self.deletePage)
55+ yield (None, ((bool, str),), self.deleteAttachment)
56+ yield ('WIKI_VIEW', ((list, str),), self.listLinks)
57+ yield ('WIKI_VIEW', ((str, str),), self.wikiToHtml)
58+
59+ def _page_info(self, name, when, author, version, comment):
60+ return dict(name=name, lastModified=when,
61+ author=author, version=int(version), comment=comment)
62+
63+ def getRecentChanges(self, req, since):
64+ """ Get list of changed pages since timestamp """
65+ since = to_utimestamp(since)
66+ wiki_realm = Resource('wiki')
67+ db = self.env.get_db_cnx()
68+ cursor = db.cursor()
69+ cursor.execute('SELECT name, max(time), author, version, comment FROM wiki'
70+ ' WHERE time >= %s GROUP BY name ORDER BY max(time) DESC', (since,))
71+ result = []
72+ for name, when, author, version, comment in cursor:
73+ if 'WIKI_VIEW' in req.perm(wiki_realm(id=name, version=version)):
74+ result.append(
75+ self._page_info(name, from_utimestamp(when),
76+ author, version, comment))
77+ return result
78+
79+ def getRPCVersionSupported(self, req):
80+ """ Returns 2 with this version of the Trac API. """
81+ return 2
82+
83+ def getPage(self, req, pagename, version=None):
84+ """ Get the raw Wiki text of page, latest version. """
85+ page = WikiPage(self.env, pagename, version)
86+ req.perm(page.resource).require('WIKI_VIEW')
87+ if page.exists:
88+ return page.text
89+ else:
90+ msg = 'Wiki page "%s" does not exist' % pagename
91+ if version is not None:
92+ msg += ' at version %s' % version
93+ raise ResourceNotFound(msg)
94+
95+ def getPageHTML(self, req, pagename, version=None):
96+ """ Return page in rendered HTML, latest version. """
97+ text = self.getPage(req, pagename, version)
98+ resource = Resource('wiki', pagename, version)
99+ context = Context.from_request(req, resource, absurls=True)
100+ html = format_to_html(self.env, context, text)
101+ return '<html><body>%s</body></html>' % html
102+
103+ def getAllPages(self, req):
104+ """ Returns a list of all pages. The result is an array of utf8 pagenames. """
105+ pages = []
106+ for page in self.wiki.get_pages():
107+ if 'WIKI_VIEW' in req.perm(Resource('wiki', page)):
108+ pages.append(page)
109+ return pages
110+
111+ def getPageInfo(self, req, pagename, version=None):
112+ """ Returns information about the given page. """
113+ page = WikiPage(self.env, pagename, version)
114+ req.perm(page.resource).require('WIKI_VIEW')
115+ if page.exists:
116+ last_update = page.get_history().next()
117+ return self._page_info(page.name, last_update[1],
118+ last_update[2], page.version, page.comment)
119+
120+ def putPage(self, req, pagename, content, attributes):
121+ """ writes the content of the page. """
122+ page = WikiPage(self.env, pagename)
123+ if page.readonly:
124+ req.perm(page.resource).require('WIKI_ADMIN')
125+ elif not page.exists:
126+ req.perm(page.resource).require('WIKI_CREATE')
127+ else:
128+ req.perm(page.resource).require('WIKI_MODIFY')
129+
130+ page.text = content
131+ if req.perm(page.resource).has_permission('WIKI_ADMIN'):
132+ page.readonly = attributes.get('readonly') and 1 or 0
133+
134+ page.save(attributes.get('author', req.authname),
135+ attributes.get('comment'), req.remote_addr)
136+ return True
137+
138+ def deletePage(self, req, name, version=None):
139+ """Delete a Wiki page (all versions) or a specific version by
140+ including an optional version number. Attachments will also be
141+ deleted if page no longer exists. Returns True for success."""
142+ wp = WikiPage(self.env, name, version)
143+ req.perm(wp.resource).require('WIKI_DELETE')
144+ try:
145+ wp.delete(version)
146+ return True
147+ except:
148+ return False
149+
150+ def listAttachments(self, req, pagename):
151+ """ Lists attachments on a given page. """
152+ for a in Attachment.select(self.env, 'wiki', pagename):
153+ if 'ATTACHMENT_VIEW' in req.perm(a.resource):
154+ yield pagename + '/' + a.filename
155+
156+ def getAttachment(self, req, path):
157+ """ returns the content of an attachment. """
158+ pagename, filename = os.path.split(path)
159+ attachment = Attachment(self.env, 'wiki', pagename, filename)
160+ req.perm(attachment.resource).require('ATTACHMENT_VIEW')
161+ return Binary(attachment.open().read())
162+
163+ def putAttachment(self, req, path, data):
164+ """ (over)writes an attachment. Returns True if successful.
165+
166+ This method is compatible with WikiRPC. `putAttachmentEx` has a more
167+ extensive set of (Trac-specific) features. """
168+ pagename, filename = os.path.split(path)
169+ self.putAttachmentEx(req, pagename, filename, None, data)
170+ return True
171+
172+ def putAttachmentEx(self, req, pagename, filename, description, data, replace=True):
173+ """ Attach a file to a Wiki page. Returns the (possibly transformed)
174+ filename of the attachment.
175+
176+ Use this method if you don't care about WikiRPC compatibility. """
177+ if not WikiPage(self.env, pagename).exists:
178+ raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
179+ if replace:
180+ try:
181+ attachment = Attachment(self.env, 'wiki', pagename, filename)
182+ req.perm(attachment.resource).require('ATTACHMENT_DELETE')
183+ attachment.delete()
184+ except TracError:
185+ pass
186+ attachment = Attachment(self.env, 'wiki', pagename)
187+ req.perm(attachment.resource).require('ATTACHMENT_CREATE')
188+ attachment.author = req.authname
189+ attachment.description = description
190+ attachment.insert(filename, StringIO(data.data), len(data.data))
191+ return attachment.filename
192+
193+ def deleteAttachment(self, req, path):
194+ """ Delete an attachment. """
195+ pagename, filename = os.path.split(path)
196+ if not WikiPage(self.env, pagename).exists:
197+ raise ResourceNotFound, 'Wiki page "%s" does not exist' % pagename
198+ attachment = Attachment(self.env, 'wiki', pagename, filename)
199+ req.perm(attachment.resource).require('ATTACHMENT_DELETE')
200+ attachment.delete()
201+ return True
202+
203+ def listLinks(self, req, pagename):
204+ """ ''Not implemented'' """
205+ return []
206+
207+ def wikiToHtml(self, req, text):
208+ """ Render arbitrary Wiki text as HTML. """
209+ return unicode(wiki_to_html(text, self.env, req, absurls=1))
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/xml_rpc.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/.svn/text-base/xml_rpc.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,205 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2005-2008 ::: Alec Thomas (alec@swapoff.org)
6+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
7+"""
8+
9+import datetime
10+import time
11+import xmlrpclib
12+
13+import genshi
14+
15+from trac.core import *
16+from trac.perm import PermissionError
17+from trac.resource import ResourceNotFound
18+from trac.util.datefmt import utc
19+from trac.util.text import to_unicode
20+from trac.web.api import RequestDone
21+
22+from tracrpc.api import XMLRPCSystem, IRPCProtocol, Binary, \
23+ RPCError, MethodNotFound, ProtocolException, ServiceException
24+from tracrpc.util import empty, prepare_docs
25+
26+__all__ = ['XmlRpcProtocol']
27+
28+def to_xmlrpc_datetime(dt):
29+ """ Convert a datetime.datetime object to a xmlrpclib DateTime object """
30+ return xmlrpclib.DateTime(dt.utctimetuple())
31+
32+def from_xmlrpc_datetime(data):
33+ """Return datetime (in utc) from XMLRPC datetime string (is always utc)"""
34+ t = list(time.strptime(data.value, "%Y%m%dT%H:%M:%S")[0:6])
35+ return apply(datetime.datetime, t, {'tzinfo': utc})
36+
37+class XmlRpcProtocol(Component):
38+ r"""
39+ There should be XML-RPC client implementations available for all
40+ popular programming languages.
41+ Example call using `curl`:
42+
43+ {{{
44+ user: ~ > cat body.xml
45+ <?xml version="1.0"?>
46+ <methodCall>
47+ <methodName>wiki.getPage</methodName>
48+ <params>
49+ <param><string>WikiStart</string></param>
50+ </params>
51+ </methodCall>
52+
53+ user: ~ > curl -H "Content-Type: application/xml" --data @body.xml ${req.abs_href.rpc()}
54+ <?xml version='1.0'?>
55+ <methodResponse>
56+ <params>
57+ <param>
58+ <value><string>= Welcome to....
59+ }}}
60+
61+ The following snippet illustrates how to perform authenticated calls in Python.
62+
63+ {{{
64+ >>> from xmlrpclib import ServerProxy
65+ >>> p = ServerProxy('${req.abs_href.login('rpc').replace('://', '://%s:your_password@' % authname)}')
66+ >>> p.system.getAPIVersion()
67+ [${', '.join(rpc.version.split('.'))}]
68+ }}}
69+ """
70+
71+ implements(IRPCProtocol)
72+
73+ # IRPCProtocol methods
74+
75+ def rpc_info(self):
76+ return ('XML-RPC', prepare_docs(self.__doc__))
77+
78+ def rpc_match(self):
79+ # Legacy path xmlrpc provided for backwards compatibility:
80+ # Using this order to get better docs
81+ yield ('rpc', 'application/xml')
82+ yield ('xmlrpc', 'application/xml')
83+ yield ('rpc', 'text/xml')
84+ yield ('xmlrpc', 'text/xml')
85+
86+ def parse_rpc_request(self, req, content_type):
87+ """ Parse XML-RPC requests."""
88+ try:
89+ args, method = xmlrpclib.loads(
90+ req.read(int(req.get_header('Content-Length'))))
91+ except Exception, e:
92+ self.log.debug("RPC(xml) parse error: %s", to_unicode(e))
93+ raise ProtocolException(xmlrpclib.Fault(-32700, to_unicode(e)))
94+ else :
95+ self.log.debug("RPC(xml) call by '%s', method '%s' with args: %s" \
96+ % (req.authname, method, repr(args)))
97+ args = self._normalize_xml_input(args)
98+ return {'method' : method, 'params' : args}
99+
100+ def send_rpc_result(self, req, result):
101+ """Send the result of the XML-RPC call back to the client."""
102+ rpcreq = req.rpc
103+ method = rpcreq.get('method')
104+ self.log.debug("RPC(xml) '%s' result: %s" % (
105+ method, repr(result)))
106+ result = tuple(self._normalize_xml_output([result]))
107+ self._send_response(req,
108+ xmlrpclib.dumps(result, methodresponse=True), rpcreq['mimetype'])
109+
110+ def send_rpc_error(self, req, e):
111+ """Send an XML-RPC fault message back to the caller"""
112+ rpcreq = req.rpc
113+ fault = None
114+ if isinstance(e, ProtocolException):
115+ fault = e._exc
116+ elif isinstance(e, ServiceException):
117+ e = e._exc
118+ elif isinstance(e, MethodNotFound):
119+ fault = xmlrpclib.Fault(-32601, to_unicode(e))
120+ elif isinstance(e, PermissionError):
121+ fault = xmlrpclib.Fault(403, to_unicode(e))
122+ elif isinstance(e, ResourceNotFound):
123+ fault = xmlrpclib.Fault(404, to_unicode(e))
124+
125+ if fault is not None :
126+ self._send_response(req, xmlrpclib.dumps(fault), rpcreq['mimetype'])
127+ else :
128+ self.log.error(e)
129+ import traceback
130+ from tracrpc.util import StringIO
131+ out = StringIO()
132+ traceback.print_exc(file = out)
133+ self.log.error(out.getvalue())
134+ err_code = hasattr(e, 'code') and e.code or 1
135+ method = rpcreq.get('method')
136+ self._send_response(req,
137+ xmlrpclib.dumps(
138+ xmlrpclib.Fault(err_code,
139+ "'%s' while executing '%s()'" % (str(e), method))))
140+
141+ # Internal methods
142+
143+ def _send_response(self, req, response, content_type='application/xml'):
144+ response = to_unicode(response).encode("utf-8")
145+ req.send_response(200)
146+ req.send_header('Content-Type', content_type)
147+ req.send_header('Content-Length', len(response))
148+ req.end_headers()
149+ req.write(response)
150+ raise RequestDone
151+
152+ def _normalize_xml_input(self, args):
153+ """ Normalizes arguments (at any level - traversing dicts and lists):
154+ 1. xmlrpc.DateTime is converted to Python datetime
155+ 2. tracrpc.api.Binary => xmlrpclib.Binary
156+ 2. String line-endings same as from web (`\n` => `\r\n`)
157+ """
158+ new_args = []
159+ for arg in args:
160+ # self.env.log.debug("arg %s, type %s" % (arg, type(arg)))
161+ if isinstance(arg, xmlrpclib.DateTime):
162+ new_args.append(from_xmlrpc_datetime(arg))
163+ elif isinstance(arg, xmlrpclib.Binary):
164+ arg.__class__ = Binary
165+ new_args.append(arg)
166+ elif isinstance(arg, basestring):
167+ new_args.append(arg.replace("\n", "\r\n"))
168+ elif isinstance(arg, dict):
169+ for key, val in arg.items():
170+ arg[key], = self._normalize_xml_input([val])
171+ new_args.append(arg)
172+ elif isinstance(arg, (list, tuple)):
173+ new_args.append(self._normalize_xml_input(arg))
174+ else:
175+ new_args.append(arg)
176+ return new_args
177+
178+ def _normalize_xml_output(self, result):
179+ """ Normalizes and converts output (traversing it):
180+ 1. None => ''
181+ 2. datetime => xmlrpclib.DateTime
182+ 3. Binary => xmlrpclib.Binary
183+ 4. genshi.builder.Fragment|genshi.core.Markup => unicode
184+ """
185+ new_result = []
186+ for res in result:
187+ if isinstance(res, datetime.datetime):
188+ new_result.append(to_xmlrpc_datetime(res))
189+ elif isinstance(res, Binary):
190+ res.__class__ = xmlrpclib.Binary
191+ new_result.append(res)
192+ elif res is None or res is empty:
193+ new_result.append('')
194+ elif isinstance(res, (genshi.builder.Fragment, \
195+ genshi.core.Markup)):
196+ new_result.append(to_unicode(res))
197+ elif isinstance(res, dict):
198+ for key, val in res.items():
199+ res[key], = self._normalize_xml_output([val])
200+ new_result.append(res)
201+ elif isinstance(res, list) or isinstance(res, tuple):
202+ new_result.append(self._normalize_xml_output(res))
203+ else:
204+ new_result.append(res)
205+ return new_result
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/all-wcprops
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/all-wcprops Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,17 @@
1+K 25
2+svn:wc:ra_dav:version-url
3+V 52
4+/svn/!svn/ver/7970/xmlrpcplugin/trunk/tracrpc/htdocs
5+END
6+rpc.js
7+K 25
8+svn:wc:ra_dav:version-url
9+V 59
10+/svn/!svn/ver/7970/xmlrpcplugin/trunk/tracrpc/htdocs/rpc.js
11+END
12+rpc.css
13+K 25
14+svn:wc:ra_dav:version-url
15+V 60
16+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/htdocs/rpc.css
17+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/entries
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/entries Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,96 @@
1+10
2+
3+dir
4+10247
5+http://trac-hacks.org/svn/xmlrpcplugin/trunk/tracrpc/htdocs
6+http://trac-hacks.org/svn
7+
8+
9+
10+2010-05-21T10:25:18.560265Z
11+7970
12+osimons
13+
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+7322e99d-02ea-0310-aa39-e9a107903beb
28+
29+rpc.js
30+file
31+
32+
33+
34+
35+2010-05-21T10:25:18.560265Z
36+126f1d53406e4cdb11bfcba6e97ddd87
37+2010-05-21T10:25:18.560265Z
38+7970
39+osimons
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+
55+
56+
57+
58+
59+
60+
61+1096
62+
63+rpc.css
64+file
65+
66+
67+
68+
69+2010-05-02T23:49:30.648005Z
70+d5914d2316007cf757b984f34109862c
71+2010-05-02T23:49:30.648005Z
72+7916
73+osimons
74+
75+
76+
77+
78+
79+
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
93+
94+
95+201
96+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/text-base/rpc.css.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/text-base/rpc.css.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,13 @@
1+#rpc-toc {
2+ display: none;
3+ float: right;
4+ width: 15em;
5+}
6+#rpc-toc.wiki-toc h4 {
7+ text-align: center;
8+}
9+#rpc-toc ul {
10+ margin: 0;
11+ padding: 0.3em 0.15em;
12+ list-style-type: none;
13+}
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/text-base/rpc.js.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/htdocs/.svn/text-base/rpc.js.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,28 @@
1+(function($) {
2+ $(document).ready(function () {
3+ // Create a Table of Contents (TOC)
4+ $('#content .wikipage')
5+ .prepend('<div id="rpc-toc" class="wiki-toc"><h4>Contents</h4><ul /></div>');
6+ function toc_entry(_this, item) {
7+ return $('<li><a href="#' + _this.id + '" title="'
8+ + $(item).text().replace(/^\s+|\s+$/g, '')
9+ + '">' + _this.id.replace(/^rpc\./, '') + '</a></li>');
10+ }
11+ var ul = $('#rpc-toc ul');
12+ $("#content").find("*[id]").each(function(index, item) {
13+ var elem = undefined;
14+ if (this.tagName == 'H2') {
15+ elem = toc_entry(this, item);
16+ elem.css('padding-top', '0.5em');
17+ }
18+ if (this.tagName == 'H3') {
19+ elem = toc_entry(this, item);
20+ elem.css('padding-left', '1.2em');
21+ }
22+ ul.append(elem);
23+ });
24+ $('#rpc-toc').toggle();
25+ // Add anchors to headings
26+ $("#content").find("h2,h3").addAnchor("Link here");
27+ });
28+})(jQuery);
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/all-wcprops
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/all-wcprops Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,11 @@
1+K 25
2+svn:wc:ra_dav:version-url
3+V 55
4+/svn/!svn/ver/7970/xmlrpcplugin/trunk/tracrpc/templates
5+END
6+rpc.html
7+K 25
8+svn:wc:ra_dav:version-url
9+V 64
10+/svn/!svn/ver/7970/xmlrpcplugin/trunk/tracrpc/templates/rpc.html
11+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/entries
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/entries Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,62 @@
1+10
2+
3+dir
4+10247
5+http://trac-hacks.org/svn/xmlrpcplugin/trunk/tracrpc/templates
6+http://trac-hacks.org/svn
7+
8+
9+
10+2010-05-21T10:25:18.560265Z
11+7970
12+osimons
13+
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+7322e99d-02ea-0310-aa39-e9a107903beb
28+
29+rpc.html
30+file
31+
32+
33+
34+
35+2010-05-21T10:25:18.560265Z
36+17d2c0f77369d699fce59f6b557a0b59
37+2010-05-21T10:25:18.560265Z
38+7970
39+osimons
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+
55+
56+
57+
58+
59+
60+
61+3463
62+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/text-base/rpc.html.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/templates/.svn/text-base/rpc.html.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,86 @@
1+<!DOCTYPE html
2+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml"
5+ xmlns:py="http://genshi.edgewall.org/"
6+ xmlns:xi="http://www.w3.org/2001/XInclude">
7+ <xi:include href="layout.html" />
8+ <xi:include href="macros.html" />
9+ <head>
10+ <title>Remote Procedure Call (RPC)</title>
11+ </head>
12+
13+ <body>
14+ <div id="content" class="wiki">
15+ <div class="wikipage searchable">
16+ <h1>Remote Procedure Call (RPC)</h1>
17+ <p><strong>Installed API version :</strong> ${rpc.version} </p>
18+ <h2 id="Protocols">Protocol reference:</h2>
19+ <p>Below you will find a detailed description of all the RPC
20+ protocols installed in this environment. This includes
21+ supported content types as well as target URLs for
22+ anonymous and authenticated access. Use this
23+ information to interact with this environment from
24+ a remote location.
25+ </p>
26+ <p>Libraries for remote procedure calls and parsing exists
27+ for most major languages and platforms - use a tested, standard library
28+ for consistent results.</p>
29+ <dl py:for="protocol in rpc.protocols">
30+ <h3 id="${protocol[0]}">${protocol[0]}</h3>
31+ <dd>
32+ <p>For ${protocol[0]} protocol, use any one of:</p>
33+ <ul>
34+ <li py:for="ct, _ct_group in groupby(protocol[2], lambda (_, x) : x)">
35+ <tt>{'Content-Type': '$ct'}</tt> header with request to:
36+ <py:with vars="ct_group = list(_ct_group)">
37+ <ul>
38+ <li py:for="h, _ in ct_group">
39+ <a href="${req.abs_href(h)}">${req.abs_href(h)}</a>
40+ for anonymous access
41+ </li>
42+ <li py:for="h, _ in ct_group">
43+ <a href="${req.abs_href.login(h)}">${req.abs_href.login(h)}</a>
44+ for authenticated access
45+ </li>
46+ </ul>
47+ </py:with>
48+ </li>
49+ </ul>
50+ <div>
51+ ${wiki_to_html(context, expand_docs(protocol[1], locals()['__data__']))}
52+ </div>
53+ </dd>
54+ </dl>
55+
56+ <h2 id="Methods">RPC exported functions</h2>
57+
58+ <div id="searchable">
59+ <dl py:for="key in sorted(rpc.functions)" py:with="namespace = rpc.functions[key]">
60+ <h3 id="${'rpc.' + to_unicode(namespace.namespace)}">
61+ ${namespace.namespace} - ${namespace.description}
62+ </h3>
63+ <dd>
64+ <table class="listing tickets">
65+ <thead>
66+ <tr>
67+ <th style="width:40%">Function</th>
68+ <th style="width:45%">Description</th>
69+ <th style="width:15%">Permission required</th>
70+ </tr>
71+ </thead>
72+ <tbody py:for="idx, function in enumerate(namespace.methods)">
73+ <tr class="${'color3-' + (idx % 2 == 0 and 'even' or 'odd')}">
74+ <td style="padding-left:4em;text-indent:-4em">${function[0]}</td>
75+ <td>${function[1]}</td>
76+ <td>${function[2] or "By resource"}</td>
77+ </tr>
78+ </tbody>
79+ </table>
80+ </dd>
81+ </dl>
82+ </div>
83+ </div>
84+ </div>
85+ </body>
86+</html>
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/all-wcprops
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/all-wcprops Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,47 @@
1+K 25
2+svn:wc:ra_dav:version-url
3+V 51
4+/svn/!svn/ver/9913/xmlrpcplugin/trunk/tracrpc/tests
5+END
6+api.py
7+K 25
8+svn:wc:ra_dav:version-url
9+V 58
10+/svn/!svn/ver/7916/xmlrpcplugin/trunk/tracrpc/tests/api.py
11+END
12+web_ui.py
13+K 25
14+svn:wc:ra_dav:version-url
15+V 61
16+/svn/!svn/ver/7971/xmlrpcplugin/trunk/tracrpc/tests/web_ui.py
17+END
18+json_rpc.py
19+K 25
20+svn:wc:ra_dav:version-url
21+V 63
22+/svn/!svn/ver/7966/xmlrpcplugin/trunk/tracrpc/tests/json_rpc.py
23+END
24+__init__.py
25+K 25
26+svn:wc:ra_dav:version-url
27+V 63
28+/svn/!svn/ver/7966/xmlrpcplugin/trunk/tracrpc/tests/__init__.py
29+END
30+ticket.py
31+K 25
32+svn:wc:ra_dav:version-url
33+V 61
34+/svn/!svn/ver/9913/xmlrpcplugin/trunk/tracrpc/tests/ticket.py
35+END
36+wiki.py
37+K 25
38+svn:wc:ra_dav:version-url
39+V 59
40+/svn/!svn/ver/9818/xmlrpcplugin/trunk/tracrpc/tests/wiki.py
41+END
42+xml_rpc.py
43+K 25
44+svn:wc:ra_dav:version-url
45+V 62
46+/svn/!svn/ver/9262/xmlrpcplugin/trunk/tracrpc/tests/xml_rpc.py
47+END
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/entries
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/entries Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,266 @@
1+10
2+
3+dir
4+10247
5+http://trac-hacks.org/svn/xmlrpcplugin/trunk/tracrpc/tests
6+http://trac-hacks.org/svn
7+
8+
9+
10+2011-03-02T03:58:15.256047Z
11+9913
12+osimons
13+
14+
15+
16+
17+
18+
19+
20+
21+
22+
23+
24+
25+
26+
27+7322e99d-02ea-0310-aa39-e9a107903beb
28+
29+api.py
30+file
31+
32+
33+
34+
35+2010-05-02T23:49:30.648005Z
36+9892e99b01b70e6ef59b06391a6c42af
37+2010-05-02T23:49:30.648005Z
38+7916
39+osimons
40+
41+
42+
43+
44+
45+
46+
47+
48+
49+
50+
51+
52+
53+
54+
55+
56+
57+
58+
59+
60+
61+5519
62+
63+web_ui.py
64+file
65+
66+
67+
68+
69+2010-05-21T10:35:35.541189Z
70+d653c2922853a1cfe7c8e30df003f0eb
71+2010-05-21T10:35:35.541189Z
72+7971
73+osimons
74+
75+
76+
77+
78+
79+
80+
81+
82+
83+
84+
85+
86+
87+
88+
89+
90+
91+
92+
93+
94+
95+4778
96+
97+json_rpc.py
98+file
99+
100+
101+
102+
103+2010-05-18T22:08:17.428696Z
104+e4c6ba6a4c1fcf9718172edc88c1987d
105+2010-05-18T22:08:17.428696Z
106+7966
107+osimons
108+
109+
110+
111+
112+
113+
114+
115+
116+
117+
118+
119+
120+
121+
122+
123+
124+
125+
126+
127+
128+
129+9894
130+
131+__init__.py
132+file
133+
134+
135+
136+
137+2010-05-18T22:08:17.428696Z
138+fbd961a14031adfaf8e56c5c899973fe
139+2010-05-18T22:08:17.428696Z
140+7966
141+osimons
142+
143+
144+
145+
146+
147+
148+
149+
150+
151+
152+
153+
154+
155+
156+
157+
158+
159+
160+
161+
162+
163+4878
164+
165+ticket.py
166+file
167+
168+
169+
170+
171+2011-03-02T03:58:15.256047Z
172+c5a83de2c9a5e692783894f4119b5b22
173+2011-03-02T03:58:15.256047Z
174+9913
175+osimons
176+
177+
178+
179+
180+
181+
182+
183+
184+
185+
186+
187+
188+
189+
190+
191+
192+
193+
194+
195+
196+
197+15205
198+
199+wiki.py
200+file
201+
202+
203+
204+
205+2011-02-04T00:18:16.742902Z
206+14f37d34cf35d2aac4a6e77137829dfd
207+2011-02-04T00:18:16.742902Z
208+9818
209+osimons
210+
211+
212+
213+
214+
215+
216+
217+
218+
219+
220+
221+
222+
223+
224+
225+
226+
227+
228+
229+
230+
231+3920
232+
233+xml_rpc.py
234+file
235+
236+
237+
238+
239+2010-10-07T18:09:30.812825Z
240+9a097de8b3fa9a155ae8f89369568020
241+2010-10-07T18:09:30.812825Z
242+9262
243+osimons
244+
245+
246+
247+
248+
249+
250+
251+
252+
253+
254+
255+
256+
257+
258+
259+
260+
261+
262+
263+
264+
265+4817
266+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/__init__.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/__init__.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,121 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+import os
10+import time
11+import urllib2
12+
13+try:
14+ from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
15+
16+ if not hasattr(SvnFunctionalTestEnvironment, 'init'):
17+ raise Exception("\nTrac version is out of date. " \
18+ "Tests require minimum Trac 0.11.5dev r8303 to run.")
19+
20+ class RpcTestEnvironment(SvnFunctionalTestEnvironment):
21+
22+ def __del__(self):
23+ print "\nStopping web server...\n"
24+ self.stop()
25+ if hasattr(SvnFunctionalTestEnvironment, '__del__'):
26+ SvnFunctionalTestEnvironment.__del__(self)
27+
28+ def init(self):
29+ self.trac_src = os.path.realpath(os.path.join(
30+ __import__('trac', []).__file__, '..' , '..'))
31+ print "\nFound Trac source: %s" % self.trac_src
32+ SvnFunctionalTestEnvironment.init(self)
33+ self.url = "%s:%s" % (self.url, self.port)
34+
35+ def post_create(self, env):
36+ print "Enabling RPC plugin and permissions..."
37+ env.config.set('components', 'tracrpc.*', 'enabled')
38+ env.config.save()
39+ self.getLogger = lambda : env.log
40+ self._tracadmin('permission', 'add', 'anonymous', 'XML_RPC')
41+ print "Created test environment: %s" % self.dirname
42+ parts = urllib2.urlparse.urlsplit(self.url)
43+ # Regular URIs
44+ self.url_anon = '%s://%s/rpc' % (parts[0], parts[1])
45+ self.url_auth = '%s://%s/login/rpc' % (parts[0], parts[1])
46+ # URIs with user:pass as part of URL
47+ self.url_user = '%s://user:user@%s/login/xmlrpc' % \
48+ (parts[0], parts[1])
49+ self.url_admin = '%s://admin:admin@%s/login/xmlrpc' % \
50+ (parts[0], parts[1])
51+ SvnFunctionalTestEnvironment.post_create(self, env)
52+ print "Starting web server: %s" % self.url
53+ self.restart()
54+
55+ def _tracadmin(self, *args, **kwargs):
56+ do_wait = kwargs.pop('wait', False)
57+ SvnFunctionalTestEnvironment._tracadmin(self, *args, **kwargs)
58+ if do_wait: # Delay to ensure command executes and caches resets
59+ time.sleep(5)
60+
61+ rpc_testenv = RpcTestEnvironment(os.path.realpath(os.path.join(
62+ os.path.realpath(__file__), '..', '..', '..', 'rpctestenv')),
63+ '8765', 'http://127.0.0.1')
64+
65+ def test_suite():
66+ suite = unittest.TestSuite()
67+ import tracrpc.tests.api
68+ suite.addTest(tracrpc.tests.api.test_suite())
69+ import tracrpc.tests.xml_rpc
70+ suite.addTest(tracrpc.tests.xml_rpc.test_suite())
71+ import tracrpc.tests.json_rpc
72+ suite.addTest(tracrpc.tests.json_rpc.test_suite())
73+ import tracrpc.tests.ticket
74+ suite.addTest(tracrpc.tests.ticket.test_suite())
75+ import tracrpc.tests.wiki
76+ suite.addTest(tracrpc.tests.wiki.test_suite())
77+ import tracrpc.tests.web_ui
78+ suite.addTest(tracrpc.tests.web_ui.test_suite())
79+ return suite
80+
81+except Exception, e:
82+ print e
83+ print "Trac test infrastructure not available."
84+ print "Install Trac as 'python setup.py develop' (run Trac from source).\n"
85+ test_suite = unittest.TestSuite() # return empty suite
86+
87+ TracRpcTestCase = unittest.TestCase
88+else :
89+ __unittest = 1 # Do not show this module in tracebacks
90+ class TracRpcTestCase(unittest.TestCase):
91+ def setUp(self):
92+ log = rpc_testenv.get_trac_environment().log
93+ log.info('=' * 70)
94+ log.info('(TEST) Starting %s.%s',
95+ self.__class__.__name__,
96+ self._testMethodName)
97+ log.info('=' * 70)
98+
99+ def failUnlessRaises(self, excClass, callableObj, *args, **kwargs):
100+ """Enhanced assertions to detect exceptions."""
101+ try:
102+ callableObj(*args, **kwargs)
103+ except excClass, e:
104+ return e
105+ except self.failureException :
106+ raise
107+ except Exception, e :
108+ if hasattr(excClass, '__name__'): excName = excClass.__name__
109+ else: excName = str(excClass)
110+
111+ if hasattr(e, '__name__'): excMsg = e.__name__
112+ else: excMsg = str(e)
113+
114+ raise self.failureException("\n\nExpected %s\n\nGot %s : %s" % (
115+ excName, e.__class__.__name__, excMsg))
116+ else:
117+ if hasattr(excClass,'__name__'): excName = excClass.__name__
118+ else: excName = str(excClass)
119+ raise self.failureException, "Expected %s\n\nNothing raised" % excName
120+
121+ assertRaises = failUnlessRaises
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/api.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/api.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,130 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import os
9+import unittest
10+import urllib2
11+
12+from tracrpc.tests import rpc_testenv, TracRpcTestCase
13+
14+from tracrpc.api import IRPCProtocol
15+
16+from trac.core import *
17+from trac.test import Mock
18+
19+class ProtocolProviderTestCase(TracRpcTestCase):
20+
21+ def setUp(self):
22+ TracRpcTestCase.setUp(self)
23+
24+ def tearDown(self):
25+ TracRpcTestCase.tearDown(self)
26+
27+ def test_invalid_content_type(self):
28+ req = urllib2.Request(rpc_testenv.url_anon,
29+ headers={'Content-Type': 'text/plain'},
30+ data='Fail! No RPC for text/plain')
31+ try:
32+ resp = urllib2.urlopen(req)
33+ self.fail("Expected urllib2.HTTPError")
34+ except urllib2.HTTPError, e:
35+ self.assertEquals(e.code, 415)
36+ self.assertEquals(e.msg, "Unsupported Media Type")
37+ self.assertEquals(e.fp.read(),
38+ "No protocol matching Content-Type 'text/plain' at path '/rpc'.")
39+
40+ def test_valid_provider(self):
41+ # Confirm the request won't work before adding plugin
42+ req = urllib2.Request(rpc_testenv.url_anon,
43+ headers={'Content-Type': 'application/x-tracrpc-test'},
44+ data="Fail! No RPC for application/x-tracrpc-test")
45+ try:
46+ resp = urllib2.urlopen(req)
47+ self.fail("Expected urllib2.HTTPError")
48+ except urllib2.HTTPError, e:
49+ self.assertEquals(e.code, 415)
50+ # Make a new plugin
51+ provider = os.path.join(rpc_testenv.tracdir, 'plugins', 'DummyProvider.py')
52+ open(provider, 'w').write(
53+ "from trac.core import *\n"
54+ "from tracrpc.api import *\n"
55+ "class DummyProvider(Component):\n"
56+ " implements(IRPCProtocol)\n"
57+ " def rpc_info(self):\n"
58+ " return ('TEST-RPC', 'No Docs!')\n"
59+ " def rpc_match(self):\n"
60+ " yield ('rpc', 'application/x-tracrpc-test')\n"
61+ " def parse_rpc_request(self, req, content_type):\n"
62+ " return {'method' : 'system.getAPIVersion'}\n"
63+ " def send_rpc_error(self, req, e):\n"
64+ " rpcreq = req.rpc\n"
65+ " req.send_error(None, template='', content_type=rpcreq['mimetype'],\n"
66+ " status=500, env=None, data='Test failure ')\n"
67+ " def send_rpc_result(self, req, result):\n"
68+ " rpcreq = req.rpc\n"
69+ " # raise KeyError('Here')\n"
70+ " response = 'Got a result!'\n"
71+ " req.send(response, rpcreq['mimetype'], 200)\n")
72+ rpc_testenv.restart()
73+ try:
74+ req = urllib2.Request(rpc_testenv.url_anon,
75+ headers={'Content-Type': 'application/x-tracrpc-test'})
76+ resp = urllib2.urlopen(req)
77+ self.assertEquals(200, resp.code)
78+ self.assertEquals("Got a result!", resp.read())
79+ self.assertEquals(resp.headers['Content-Type'],
80+ 'application/x-tracrpc-test;charset=utf-8')
81+ finally:
82+ # Clean up so that provider don't affect further tests
83+ os.unlink(provider)
84+ rpc_testenv.restart()
85+
86+ def test_general_provider_error(self):
87+ # Make a new plugin and restart server
88+ provider = os.path.join(rpc_testenv.tracdir, 'plugins', 'DummyProvider.py')
89+ open(provider, 'w').write(
90+ "from trac.core import *\n"
91+ "from tracrpc.api import *\n"
92+ "class DummyProvider(Component):\n"
93+ " implements(IRPCProtocol)\n"
94+ " def rpc_info(self):\n"
95+ " return ('TEST-RPC', 'No Docs!')\n"
96+ " def rpc_match(self):\n"
97+ " yield ('rpc', 'application/x-tracrpc-test')\n"
98+ " def parse_rpc_request(self, req, content_type):\n"
99+ " return {'method' : 'system.getAPIVersion'}\n"
100+ " def send_rpc_error(self, req, e):\n"
101+ " if isinstance(e, RPCError) :\n"
102+ " req.send_error(None, template='', \n"
103+ " content_type='text/plain',\n"
104+ " status=500, env=None, data=e.message)\n"
105+ " else :\n"
106+ " req.send_error(None, template='', \n"
107+ " content_type='text/plain',\n"
108+ " status=500, env=None, data='Test failure')\n"
109+ " def send_rpc_result(self, req, result):\n"
110+ " raise RPCError('No good.')")
111+ rpc_testenv.restart()
112+ # Make the request
113+ try:
114+ req = urllib2.Request(rpc_testenv.url_anon,
115+ headers={'Content-Type': 'application/x-tracrpc-test'})
116+ resp = urllib2.urlopen(req)
117+ except urllib2.HTTPError, e:
118+ self.assertEquals(500, e.code)
119+ self.assertEquals("No good.", e.fp.read())
120+ self.assertTrue(e.hdrs['Content-Type'].startswith('text/plain'))
121+ finally:
122+ # Clean up so that provider don't affect further tests
123+ os.unlink(provider)
124+ rpc_testenv.restart()
125+
126+def test_suite():
127+ return unittest.makeSuite(ProtocolProviderTestCase)
128+
129+if __name__ == '__main__':
130+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/json_rpc.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/json_rpc.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,222 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+import os
10+import shutil
11+import urllib2
12+
13+from tracrpc.json_rpc import json
14+from tracrpc.util import StringIO
15+
16+from tracrpc.tests import rpc_testenv, TracRpcTestCase
17+
18+class JsonModuleAvailabilityTestCase(TracRpcTestCase):
19+
20+ def setUp(self):
21+ TracRpcTestCase.setUp(self)
22+
23+ def tearDown(self):
24+ TracRpcTestCase.tearDown(self)
25+
26+ def test_json_not_available(self):
27+ if not json:
28+ # No json, so just make sure the protocol isn't there
29+ import tracrpc.json_rpc
30+ self.failIf(hasattr(tracrpc.json_rpc, 'JsonRpcProtocol'),
31+ "JsonRpcProtocol really available?")
32+ return
33+ # Module manipulation to simulate json libs not available
34+ import sys
35+ old_json = sys.modules.get('json', None)
36+ sys.modules['json'] = None
37+ old_simplejson = sys.modules.get('simplejson', None)
38+ sys.modules['simplejson'] = None
39+ if 'tracrpc.json_rpc' in sys.modules:
40+ del sys.modules['tracrpc.json_rpc']
41+ try:
42+ import tracrpc.json_rpc
43+ self.failIf(hasattr(tracrpc.json_rpc, 'JsonRpcProtocol'),
44+ "JsonRpcProtocol really available?")
45+ finally:
46+ del sys.modules['json']
47+ del sys.modules['simplejson']
48+ if old_json:
49+ sys.modules['json'] = old_json
50+ if old_simplejson:
51+ sys.modules['simplejson'] = old_simplejson
52+ if 'tracrpc.json_rpc' in sys.modules:
53+ del sys.modules['tracrpc.json_rpc']
54+ import tracrpc.json_rpc
55+ self.failIf(not hasattr(tracrpc.json_rpc, 'JsonRpcProtocol'),
56+ "What, no JsonRpcProtocol?")
57+
58+if not json:
59+ print "SKIP: json not available. Cannot run JsonTestCase."
60+ class JsonTestCase(TracRpcTestCase):
61+ pass
62+else:
63+ class JsonTestCase(TracRpcTestCase):
64+
65+ def _anon_req(self, data):
66+ req = urllib2.Request(rpc_testenv.url_anon,
67+ headers={'Content-Type': 'application/json'})
68+ req.data = json.dumps(data)
69+ resp = urllib2.urlopen(req)
70+ return json.loads(resp.read())
71+
72+ def _auth_req(self, data, user='user'):
73+ password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
74+ handler = urllib2.HTTPBasicAuthHandler(password_mgr)
75+ password_mgr.add_password(realm=None,
76+ uri=rpc_testenv.url_auth,
77+ user=user,
78+ passwd=user)
79+ req = urllib2.Request(rpc_testenv.url_auth,
80+ headers={'Content-Type': 'application/json'})
81+ req.data = json.dumps(data)
82+ resp = urllib2.build_opener(handler).open(req)
83+ return json.loads(resp.read())
84+
85+ def setUp(self):
86+ TracRpcTestCase.setUp(self)
87+
88+ def tearDown(self):
89+ TracRpcTestCase.tearDown(self)
90+
91+ def test_call(self):
92+ result = self._anon_req(
93+ {'method': 'system.listMethods', 'params': [], 'id': 244})
94+ self.assertTrue('system.methodHelp' in result['result'])
95+ self.assertEquals(None, result['error'])
96+ self.assertEquals(244, result['id'])
97+
98+ def test_multicall(self):
99+ data = {'method': 'system.multicall', 'params': [
100+ {'method': 'wiki.getAllPages', 'params': [], 'id': 1},
101+ {'method': 'wiki.getPage', 'params': ['WikiStart', 1], 'id': 2},
102+ {'method': 'ticket.status.getAll', 'params': [], 'id': 3},
103+ {'method': 'nonexisting', 'params': []}
104+ ], 'id': 233}
105+ result = self._anon_req(data)
106+ self.assertEquals(None, result['error'])
107+ self.assertEquals(4, len(result['result']))
108+ items = result['result']
109+ self.assertEquals(1, items[0]['id'])
110+ self.assertEquals(233, items[3]['id'])
111+ self.assertTrue('WikiStart' in items[0]['result'])
112+ self.assertEquals(None, items[0]['error'])
113+ self.assertTrue('Welcome' in items[1]['result'])
114+ self.assertEquals(['accepted', 'assigned', 'closed', 'new',
115+ 'reopened'], items[2]['result'])
116+ self.assertEquals(None, items[3]['result'])
117+ self.assertEquals('JSONRPCError', items[3]['error']['name'])
118+
119+ def test_datetime(self):
120+ # read and write datetime values
121+ from datetime import datetime
122+ from trac.util.datefmt import utc
123+ dt_str = "2009-06-19T16:46:00"
124+ dt_dt = datetime(2009, 06, 19, 16, 46, 00, tzinfo=utc)
125+ data = {'method': 'ticket.milestone.update',
126+ 'params': ['milestone1', {'due': {'__jsonclass__':
127+ ['datetime', dt_str]}}]}
128+ result = self._auth_req(data, user='admin')
129+ self.assertEquals(None, result['error'])
130+ result = self._auth_req({'method': 'ticket.milestone.get',
131+ 'params': ['milestone1']}, user='admin')
132+ self.assertTrue(result['result'])
133+ self.assertEquals(dt_str,
134+ result['result']['due']['__jsonclass__'][1])
135+
136+ def test_binary(self):
137+ # read and write binaries values
138+ image_url = os.path.join(rpc_testenv.trac_src, 'trac',
139+ 'htdocs', 'feed.png')
140+ image_in = StringIO(open(image_url, 'r').read())
141+ data = {'method': 'wiki.putAttachmentEx',
142+ 'params': ['TitleIndex', "feed2.png", "test image",
143+ {'__jsonclass__': ['binary',
144+ image_in.getvalue().encode("base64")]}]}
145+ result = self._auth_req(data, user='admin')
146+ self.assertEquals('feed2.png', result['result'])
147+ # Now try to get the attachment, and verify it is identical
148+ result = self._auth_req({'method': 'wiki.getAttachment',
149+ 'params': ['TitleIndex/feed2.png']}, user='admin')
150+ self.assertTrue(result['result'])
151+ image_out = StringIO(
152+ result['result']['__jsonclass__'][1].decode("base64"))
153+ self.assertEquals(image_in.getvalue(), image_out.getvalue())
154+
155+ def test_xmlrpc_permission(self):
156+ # Test returned response if not XML_RPC permission
157+ rpc_testenv._tracadmin('permission', 'remove', 'anonymous',
158+ 'XML_RPC', wait=True)
159+ try:
160+ result = self._anon_req({'method': 'system.listMethods',
161+ 'id': 'no-perm'})
162+ self.assertEquals(None, result['result'])
163+ self.assertEquals('no-perm', result['id'])
164+ self.assertEquals(403, result['error']['code'])
165+ self.assertTrue('XML_RPC' in result['error']['message'])
166+ finally:
167+ # Add back the default permission for further tests
168+ rpc_testenv._tracadmin('permission', 'add', 'anonymous',
169+ 'XML_RPC', wait=True)
170+
171+ def test_method_not_found(self):
172+ result = self._anon_req({'method': 'system.doesNotExist',
173+ 'id': 'no-method'})
174+ self.assertTrue(result['error'])
175+ self.assertEquals(result['id'], 'no-method')
176+ self.assertEquals(None, result['result'])
177+ self.assertEquals(-32601, result['error']['code'])
178+ self.assertTrue('not found' in result['error']['message'])
179+
180+ def test_wrong_argspec(self):
181+ result = self._anon_req({'method': 'system.listMethods',
182+ 'params': ['hello'], 'id': 'wrong-args'})
183+ self.assertTrue(result['error'])
184+ self.assertEquals(result['id'], 'wrong-args')
185+ self.assertEquals(None, result['result'])
186+ self.assertEquals(-32603, result['error']['code'])
187+ self.assertTrue('listMethods() takes exactly 2 arguments' \
188+ in result['error']['message'])
189+
190+ def test_call_permission(self):
191+ # Test missing call-specific permission
192+ result = self._anon_req({'method': 'ticket.component.delete',
193+ 'params': ['component1'], 'id': 2332})
194+ self.assertEquals(None, result['result'])
195+ self.assertEquals(2332, result['id'])
196+ self.assertEquals(403, result['error']['code'])
197+ self.assertEquals(result['error']['message'],
198+ 'TICKET_ADMIN privileges are required to perform this operation')
199+
200+ def test_resource_not_found(self):
201+ # A Ticket resource
202+ result = self._anon_req({'method': 'ticket.get',
203+ 'params': [2147483647], 'id': 3443})
204+ self.assertEquals(result['id'], 3443)
205+ self.assertEquals(result['error']['code'], 404)
206+ self.assertEquals(result['error']['message'],
207+ 'Ticket 2147483647 does not exist.')
208+ # A Wiki resource
209+ result = self._anon_req({'method': 'wiki.getPage',
210+ 'params': ["Test", 10], 'id': 3443})
211+ self.assertEquals(result['error']['code'], 404)
212+ self.assertEquals(result['error']['message'],
213+ 'Wiki page "Test" does not exist at version 10')
214+
215+def test_suite():
216+ test_suite = unittest.TestSuite()
217+ test_suite.addTest(unittest.makeSuite(JsonModuleAvailabilityTestCase))
218+ test_suite.addTest(unittest.makeSuite(JsonTestCase))
219+ return test_suite
220+
221+if __name__ == '__main__':
222+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/ticket.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/ticket.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,335 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+
10+import xmlrpclib
11+import os
12+import shutil
13+import datetime
14+import time
15+
16+from tracrpc.tests import rpc_testenv, TracRpcTestCase
17+
18+class RpcTicketTestCase(TracRpcTestCase):
19+
20+ def setUp(self):
21+ TracRpcTestCase.setUp(self)
22+ self.anon = xmlrpclib.ServerProxy(rpc_testenv.url_anon)
23+ self.user = xmlrpclib.ServerProxy(rpc_testenv.url_user)
24+ self.admin = xmlrpclib.ServerProxy(rpc_testenv.url_admin)
25+
26+ def tearDown(self):
27+ TracRpcTestCase.tearDown(self)
28+
29+ def test_getActions(self):
30+ tid = self.admin.ticket.create("ticket_getActions", "kjsald", {})
31+ actions = self.admin.ticket.getActions(tid)
32+ default = [['leave', 'leave', '.', []], ['resolve', 'resolve',
33+ "The resolution will be set. Next status will be 'closed'.",
34+ [['action_resolve_resolve_resolution', 'fixed',
35+ ['fixed', 'invalid', 'wontfix', 'duplicate', 'worksforme']]]],
36+ ['reassign', 'reassign',
37+ "The owner will change from (none). Next status will be 'assigned'.",
38+ [['action_reassign_reassign_owner', 'admin', []]]],
39+ ['accept', 'accept',
40+ "The owner will change from (none) to admin. Next status will be 'accepted'.", []]]
41+ # Some action text was changed in trac:changeset:9041 - adjust default for test
42+ if 'will be changed' in actions[2][2]:
43+ default[2][2] = default[2][2].replace('will change', 'will be changed')
44+ default[3][2] = default[3][2].replace('will change', 'will be changed')
45+ self.assertEquals(actions, default)
46+ self.admin.ticket.delete(tid)
47+
48+ def test_getAvailableActions_DeleteTicket(self):
49+ # http://trac-hacks.org/ticket/5387
50+ tid = self.admin.ticket.create('abc', 'def', {})
51+ self.assertEquals(False,
52+ 'delete' in self.admin.ticket.getAvailableActions(tid))
53+ env = rpc_testenv.get_trac_environment()
54+ delete_plugin = os.path.join(rpc_testenv.tracdir,
55+ 'plugins', 'DeleteTicket.py')
56+ shutil.copy(os.path.join(
57+ rpc_testenv.trac_src, 'sample-plugins', 'workflow', 'DeleteTicket.py'),
58+ delete_plugin)
59+ env.config.set('ticket', 'workflow',
60+ 'ConfigurableTicketWorkflow,DeleteTicketActionController')
61+ env.config.save()
62+ self.assertEquals(True,
63+ 'delete' in self.admin.ticket.getAvailableActions(tid))
64+ self.assertEquals(False,
65+ 'delete' in self.user.ticket.getAvailableActions(tid))
66+ env.config.set('ticket', 'workflow',
67+ 'ConfigurableTicketWorkflow')
68+ env.config.save()
69+ rpc_testenv.restart()
70+ self.assertEquals(False,
71+ 'delete' in self.admin.ticket.getAvailableActions(tid))
72+ # Clean up
73+ os.unlink(delete_plugin)
74+ rpc_testenv.restart()
75+
76+ def test_FineGrainedSecurity(self):
77+ self.assertEquals(1, self.admin.ticket.create('abc', '123', {}))
78+ self.assertEquals(2, self.admin.ticket.create('def', '456', {}))
79+ # First some non-restricted tests for comparison:
80+ self.assertRaises(xmlrpclib.Fault, self.anon.ticket.create, 'abc', 'def')
81+ self.assertEquals([1,2], self.user.ticket.query())
82+ self.assertTrue(self.user.ticket.get(2))
83+ self.assertTrue(self.user.ticket.update(1, "ok"))
84+ self.assertTrue(self.user.ticket.update(2, "ok"))
85+ # Enable security policy and test
86+ from trac.core import Component, implements
87+ from trac.perm import IPermissionPolicy
88+ policy = os.path.join(rpc_testenv.tracdir, 'plugins', 'TicketPolicy.py')
89+ open(policy, 'w').write(
90+ "from trac.core import *\n"
91+ "from trac.perm import IPermissionPolicy\n"
92+ "class TicketPolicy(Component):\n"
93+ " implements(IPermissionPolicy)\n"
94+ " def check_permission(self, action, username, resource, perm):\n"
95+ " if username == 'user' and resource and resource.id == 2:\n"
96+ " return False\n"
97+ " if username == 'anonymous' and action == 'TICKET_CREATE':\n"
98+ " return True\n")
99+ env = rpc_testenv.get_trac_environment()
100+ _old_conf = env.config.get('trac', 'permission_policies')
101+ env.config.set('trac', 'permission_policies', 'TicketPolicy,'+_old_conf)
102+ env.config.save()
103+ rpc_testenv.restart()
104+ self.assertEquals([1], self.user.ticket.query())
105+ self.assertTrue(self.user.ticket.get(1))
106+ self.assertRaises(xmlrpclib.Fault, self.user.ticket.get, 2)
107+ self.assertTrue(self.user.ticket.update(1, "ok"))
108+ self.assertRaises(xmlrpclib.Fault, self.user.ticket.update, 2, "not ok")
109+ self.assertEquals(3, self.anon.ticket.create('efg', '789', {}))
110+ # Clean, reset and simple verification
111+ env.config.set('trac', 'permission_policies', _old_conf)
112+ env.config.save()
113+ os.unlink(policy)
114+ rpc_testenv.restart()
115+ self.assertEquals([1,2,3], self.user.ticket.query())
116+ self.assertEquals(0, self.admin.ticket.delete(1))
117+ self.assertEquals(0, self.admin.ticket.delete(2))
118+ self.assertEquals(0, self.admin.ticket.delete(3))
119+
120+ def test_getRecentChanges(self):
121+ tid1 = self.admin.ticket.create("ticket_getRecentChanges", "one", {})
122+ time.sleep(1)
123+ tid2 = self.admin.ticket.create("ticket_getRecentChanges", "two", {})
124+ _id, created, modified, attributes = self.admin.ticket.get(tid2)
125+ changes = self.admin.ticket.getRecentChanges(created)
126+ try:
127+ self.assertEquals(changes, [tid2])
128+ finally:
129+ self.admin.ticket.delete(tid1)
130+ self.admin.ticket.delete(tid2)
131+
132+ def test_query_special_character_escape(self):
133+ # Note: This test only passes when using Trac 0.12+
134+ # See http://trac-hacks.org/ticket/7737
135+ if __import__('trac').__version__ < '0.12':
136+ self.fail("Known issue: Trac 0.11 does not handle escaped input properly.")
137+ summary = ["here&now", "maybe|later", "back\slash"]
138+ search = ["here\&now", "maybe\|later", "back\\slash"]
139+ tids = []
140+ for s in summary:
141+ tids.append(self.admin.ticket.create(s,
142+ "test_special_character_escape", {}))
143+ try:
144+ for i in range(0, 3):
145+ self.assertEquals([tids[i]],
146+ self.admin.ticket.query("summary=%s" % search[i]))
147+ self.assertEquals(tids.sort(),
148+ self.admin.ticket.query("summary=%s" % "|".join(search)).sort())
149+ finally:
150+ for tid in tids:
151+ self.admin.ticket.delete(tid)
152+
153+ def test_update_author(self):
154+ tid = self.admin.ticket.create("ticket_update_author", "one", {})
155+ self.admin.ticket.update(tid, 'comment1', {})
156+ time.sleep(1)
157+ self.admin.ticket.update(tid, 'comment2', {}, False, 'foo')
158+ time.sleep(1)
159+ self.user.ticket.update(tid, 'comment3', {}, False, 'should_be_rejected')
160+ changes = self.admin.ticket.changeLog(tid)
161+ self.assertEquals(3, len(changes))
162+ for when, who, what, cnum, comment, _tid in changes:
163+ self.assertTrue(comment in ('comment1', 'comment2', 'comment3'))
164+ if comment == 'comment1':
165+ self.assertEquals('admin', who)
166+ if comment == 'comment2':
167+ self.assertEquals('foo', who)
168+ if comment == 'comment3':
169+ self.assertEquals('user', who)
170+ self.admin.ticket.delete(tid)
171+
172+ def test_create_at_time(self):
173+ from trac.util.datefmt import to_datetime, utc
174+ now = to_datetime(None, utc)
175+ minus1 = now - datetime.timedelta(days=1)
176+ # create the tickets (user ticket will not be permitted to change time)
177+ one = self.admin.ticket.create("create_at_time1", "ok", {}, False,
178+ xmlrpclib.DateTime(minus1))
179+ two = self.user.ticket.create("create_at_time3", "ok", {}, False,
180+ xmlrpclib.DateTime(minus1))
181+ # get the tickets
182+ t1 = self.admin.ticket.get(one)
183+ t2 = self.admin.ticket.get(two)
184+ # check timestamps
185+ self.assertTrue(t1[1] < t2[1])
186+ self.admin.ticket.delete(one)
187+ self.admin.ticket.delete(two)
188+
189+ def test_update_at_time(self):
190+ from trac.util.datefmt import to_datetime, utc
191+ now = to_datetime(None, utc)
192+ minus1 = now - datetime.timedelta(hours=1)
193+ minus2 = now - datetime.timedelta(hours=2)
194+ tid = self.admin.ticket.create("ticket_update_at_time", "ok", {})
195+ self.admin.ticket.update(tid, 'one', {}, False, '', xmlrpclib.DateTime(minus2))
196+ self.admin.ticket.update(tid, 'two', {}, False, '', xmlrpclib.DateTime(minus1))
197+ self.user.ticket.update(tid, 'three', {}, False, '', xmlrpclib.DateTime(minus1))
198+ time.sleep(1)
199+ self.user.ticket.update(tid, 'four', {})
200+ changes = self.admin.ticket.changeLog(tid)
201+ self.assertEquals(4, len(changes))
202+ # quick test to make sure each is older than previous
203+ self.assertTrue(changes[0][0] < changes[1][0] < changes[2][0])
204+ # margin of 2 seconds for tests
205+ justnow = xmlrpclib.DateTime(now - datetime.timedelta(seconds=1))
206+ self.assertTrue(justnow <= changes[2][0])
207+ self.assertTrue(justnow <= changes[3][0])
208+ self.admin.ticket.delete(tid)
209+
210+ def test_update_non_existing(self):
211+ try:
212+ self.admin.ticket.update(3344, "a comment", {})
213+ self.fail("Allowed to update non-existing ticket???")
214+ self.admin.ticket.delete(3234)
215+ except Exception, e:
216+ self.assertTrue("Ticket 3344 does not exist." in str(e))
217+
218+ def test_update_basic(self):
219+ import time
220+ # Basic update check, no 'action' or 'time_changed'
221+ tid = self.admin.ticket.create('test_update_basic1', 'ieidnsj', {
222+ 'owner': 'osimons'})
223+ # old-style (deprecated)
224+ self.admin.ticket.update(tid, "comment1", {'component': 'component2'})
225+ self.assertEquals(2, len(self.admin.ticket.changeLog(tid)))
226+ # new-style with 'action'
227+ time.sleep(1) # avoid "columns ticket, time, field are not unique"
228+ self.admin.ticket.update(tid, "comment2", {'component': 'component1',
229+ 'action': 'leave'})
230+ self.assertEquals(4, len(self.admin.ticket.changeLog(tid)))
231+ self.admin.ticket.delete(tid)
232+
233+ def test_update_time_changed(self):
234+ # Update with collision check
235+ import datetime
236+ from tracrpc.xml_rpc import from_xmlrpc_datetime, to_xmlrpc_datetime
237+ tid = self.admin.ticket.create('test_update_time_changed', '...', {})
238+ tid, created, modified, attrs = self.admin.ticket.get(tid)
239+ then = from_xmlrpc_datetime(modified) - datetime.timedelta(minutes=1)
240+ # Unrestricted old-style update (to be removed soon)
241+ try:
242+ self.admin.ticket.update(tid, "comment1",
243+ {'_ts': to_xmlrpc_datetime(then)})
244+ except Exception, e:
245+ self.assertTrue("Ticket has been updated since last get" in str(e))
246+ # Update with 'action' to test new-style update.
247+ try:
248+ self.admin.ticket.update(tid, "comment1",
249+ {'_ts': to_xmlrpc_datetime(then),
250+ 'action': 'leave'})
251+ except Exception, e:
252+ self.assertTrue("modified by someone else" in str(e))
253+ self.admin.ticket.delete(tid)
254+
255+ def test_update_time_same(self):
256+ # Update with collision check
257+ import datetime
258+ from tracrpc.xml_rpc import from_xmlrpc_datetime, to_xmlrpc_datetime
259+
260+ # Unrestricted old-style update (to be removed soon)
261+ tid = self.admin.ticket.create('test_update_time_same', '...', {})
262+ tid, created, modified, attrs = self.admin.ticket.get(tid)
263+ ts = attrs['_ts']
264+ self.admin.ticket.update(tid, "comment1",
265+ {'_ts': ts})
266+ self.admin.ticket.delete(tid)
267+
268+ # Update with 'action' to test new-style update.
269+ tid = self.admin.ticket.create('test_update_time_same', '...', {})
270+ tid, created, modified, attrs = self.admin.ticket.get(tid)
271+ ts = attrs['_ts']
272+ self.admin.ticket.update(tid, "comment1",
273+ {'_ts': ts, 'action': 'leave'})
274+ self.admin.ticket.delete(tid)
275+
276+ def test_update_action(self):
277+ # Updating with 'action' in attributes
278+ tid = self.admin.ticket.create('test_update_action', 'ss')
279+ current = self.admin.ticket.get(tid)
280+ self.assertEqual('', current[3].get('owner', ''))
281+ updated = self.admin.ticket.update(tid, "comment1",
282+ {'action': 'reassign',
283+ 'action_reassign_reassign_owner': 'user'})
284+ self.assertEqual('user', updated[3].get('owner'))
285+ self.admin.ticket.delete(tid)
286+
287+ def test_update_action_non_existing(self):
288+ # Updating with non-existing 'action' in attributes
289+ tid = self.admin.ticket.create('test_update_action_wrong', 'ss')
290+ try:
291+ self.admin.ticket.update(tid, "comment1",
292+ {'action': 'reassign',
293+ 'action_reassign_reassign_owner': 'user'})
294+ except Exception, e:
295+ self.assertTrue("invalid action" in str(e))
296+ self.admin.ticket.delete(tid)
297+
298+ def test_update_field_non_existing(self):
299+ tid = self.admin.ticket.create('test_update_field_non_existing', 'yw3')
300+ try:
301+ self.admin.ticket.update(tid, "comment1",
302+ {'does_not_exist': 'eiwrjoer'})
303+ except Exception, e:
304+ self.assertTrue("no such column" in str(e))
305+ self.admin.ticket.delete(tid)
306+
307+
308+class RpcTicketVersionTestCase(TracRpcTestCase):
309+
310+ def setUp(self):
311+ TracRpcTestCase.setUp(self)
312+ self.anon = xmlrpclib.ServerProxy(rpc_testenv.url_anon)
313+ self.user = xmlrpclib.ServerProxy(rpc_testenv.url_user)
314+ self.admin = xmlrpclib.ServerProxy(rpc_testenv.url_admin)
315+
316+ def tearDown(self):
317+ TracRpcTestCase.tearDown(self)
318+
319+ def test_create(self):
320+ dt = xmlrpclib.DateTime(datetime.datetime.utcnow())
321+ desc = "test version"
322+ v = self.admin.ticket.version.create('9.99',
323+ {'time': dt, 'description': desc})
324+ self.failUnless('9.99' in self.admin.ticket.version.getAll())
325+ self.assertEquals({'time': dt, 'description': desc, 'name': '9.99'},
326+ self.admin.ticket.version.get('9.99'))
327+
328+def test_suite():
329+ test_suite = unittest.TestSuite()
330+ test_suite.addTest(unittest.makeSuite(RpcTicketTestCase))
331+ test_suite.addTest(unittest.makeSuite(RpcTicketVersionTestCase))
332+ return test_suite
333+
334+if __name__ == '__main__':
335+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/web_ui.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/web_ui.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,118 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+import urllib2
10+
11+from tracrpc.tests import rpc_testenv, TracRpcTestCase
12+
13+class DocumentationTestCase(TracRpcTestCase):
14+
15+ def setUp(self):
16+ TracRpcTestCase.setUp(self)
17+ password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
18+ handler = urllib2.HTTPBasicAuthHandler(password_mgr)
19+ password_mgr.add_password(realm=None,
20+ uri=rpc_testenv.url_auth,
21+ user='user', passwd='user')
22+ self.opener_user = urllib2.build_opener(handler)
23+
24+ def tearDown(self):
25+ TracRpcTestCase.tearDown(self)
26+
27+ def test_get_with_content_type(self):
28+ req = urllib2.Request(rpc_testenv.url_auth,
29+ headers={'Content-Type': 'text/html'})
30+ self.assert_rpcdocs_ok(self.opener_user, req)
31+
32+ def test_get_no_content_type(self):
33+ req = urllib2.Request(rpc_testenv.url_auth)
34+ self.assert_rpcdocs_ok(self.opener_user, req)
35+
36+ def test_post_accept(self):
37+ req = urllib2.Request(rpc_testenv.url_auth,
38+ headers={'Content-Type' : 'text/plain',
39+ 'Accept': 'application/x-trac-test,text/html'},
40+ data='Pass since client accepts HTML')
41+ self.assert_rpcdocs_ok(self.opener_user, req)
42+
43+ req = urllib2.Request(rpc_testenv.url_auth,
44+ headers={'Content-Type' : 'text/plain'},
45+ data='Fail! No content type expected')
46+ self.assert_unsupported_media_type(self.opener_user, req)
47+
48+ def test_form_submit(self):
49+ from urllib import urlencode
50+ # Explicit content type
51+ form_vars = {'result' : 'Fail! __FORM_TOKEN protection activated'}
52+ req = urllib2.Request(rpc_testenv.url_auth,
53+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
54+ data=urlencode(form_vars))
55+ self.assert_form_protect(self.opener_user, req)
56+
57+ # Implicit content type
58+ req = urllib2.Request(rpc_testenv.url_auth,
59+ headers={'Accept': 'application/x-trac-test,text/html'},
60+ data='Pass since client accepts HTML')
61+ self.assert_form_protect(self.opener_user, req)
62+
63+ def test_get_dont_accept(self):
64+ req = urllib2.Request(rpc_testenv.url_auth,
65+ headers={'Accept': 'application/x-trac-test'})
66+ self.assert_unsupported_media_type(self.opener_user, req)
67+
68+ def test_post_dont_accept(self):
69+ req = urllib2.Request(rpc_testenv.url_auth,
70+ headers={'Content-Type': 'text/plain',
71+ 'Accept': 'application/x-trac-test'},
72+ data='Fail! Client cannot process HTML')
73+ self.assert_unsupported_media_type(self.opener_user, req)
74+
75+ # Custom assertions
76+ def assert_rpcdocs_ok(self, opener, req):
77+ """Determine if RPC docs are ok"""
78+ try :
79+ resp = opener.open(req)
80+ except urllib2.HTTPError, e :
81+ self.fail("Request to '%s' failed (%s) %s" % (e.geturl(),
82+ e.code,
83+ e.fp.read()))
84+ else :
85+ self.assertEquals(200, resp.code)
86+ body = resp.read()
87+ self.assertTrue('<h3 id="XML-RPC">XML-RPC</h3>' in body)
88+ self.assertTrue('<h3 id="rpc.ticket.status">' in body)
89+
90+ def assert_unsupported_media_type(self, opener, req):
91+ """Ensure HTTP 415 is returned back to the client"""
92+ try :
93+ opener.open(req)
94+ except urllib2.HTTPError, e:
95+ self.assertEquals(415, e.code)
96+ expected = "No protocol matching Content-Type '%s' at path '%s'." % \
97+ (req.headers.get('Content-Type', 'text/plain'),
98+ '/login/rpc')
99+ got = e.fp.read()
100+ self.assertEquals(expected, got)
101+ except Exception, e:
102+ self.fail('Expected HTTP error but %s raised instead' % \
103+ (e.__class__.__name__,))
104+ else :
105+ self.fail('Expected HTTP error (415) but nothing raised')
106+
107+ def assert_form_protect(self, opener, req):
108+ e = self.assertRaises(urllib2.HTTPError, opener.open, req)
109+ self.assertEquals(400, e.code)
110+ msg = e.fp.read()
111+ self.assertTrue("Missing or invalid form token. "
112+ "Do you have cookies enabled?" in msg)
113+
114+def test_suite():
115+ return unittest.makeSuite(DocumentationTestCase)
116+
117+if __name__ == '__main__':
118+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/wiki.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/wiki.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,92 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+
10+import xmlrpclib
11+import os
12+import time
13+
14+from trac.util.compat import sorted
15+
16+from tracrpc.tests import rpc_testenv, TracRpcTestCase
17+from tracrpc.util import StringIO
18+
19+class RpcWikiTestCase(TracRpcTestCase):
20+
21+ def setUp(self):
22+ TracRpcTestCase.setUp(self)
23+ self.anon = xmlrpclib.ServerProxy(rpc_testenv.url_anon)
24+ self.user = xmlrpclib.ServerProxy(rpc_testenv.url_user)
25+ self.admin = xmlrpclib.ServerProxy(rpc_testenv.url_admin)
26+
27+ def tearDown(self):
28+ TracRpcTestCase.tearDown(self)
29+
30+ def test_attachments(self):
31+ # Note: Quite similar to the tracrpc.tests.json.JsonTestCase.test_binary
32+ image_url = os.path.join(rpc_testenv.trac_src, 'trac',
33+ 'htdocs', 'feed.png')
34+ image_in = StringIO(open(image_url, 'r').read())
35+ # Create attachment
36+ self.admin.wiki.putAttachmentEx('TitleIndex', 'feed2.png', 'test image',
37+ xmlrpclib.Binary(image_in.getvalue()))
38+ self.assertEquals(image_in.getvalue(), self.admin.wiki.getAttachment(
39+ 'TitleIndex/feed2.png').data)
40+ # Update attachment (adding new)
41+ self.admin.wiki.putAttachmentEx('TitleIndex', 'feed2.png', 'test image',
42+ xmlrpclib.Binary(image_in.getvalue()), False)
43+ self.assertEquals(image_in.getvalue(), self.admin.wiki.getAttachment(
44+ 'TitleIndex/feed2.2.png').data)
45+ # List attachments
46+ self.assertEquals(['TitleIndex/feed2.2.png', 'TitleIndex/feed2.png'],
47+ sorted(self.admin.wiki.listAttachments('TitleIndex')))
48+ # Delete both attachments
49+ self.admin.wiki.deleteAttachment('TitleIndex/feed2.png')
50+ self.admin.wiki.deleteAttachment('TitleIndex/feed2.2.png')
51+ # List attachments again
52+ self.assertEquals([], self.admin.wiki.listAttachments('TitleIndex'))
53+
54+ def test_getRecentChanges(self):
55+ self.admin.wiki.putPage('WikiOne', 'content one', {})
56+ time.sleep(1)
57+ self.admin.wiki.putPage('WikiTwo', 'content two', {})
58+ attrs2 = self.admin.wiki.getPageInfo('WikiTwo')
59+ changes = self.admin.wiki.getRecentChanges(attrs2['lastModified'])
60+ self.assertEquals(1, len(changes))
61+ self.assertEquals('WikiTwo', changes[0]['name'])
62+ self.assertEquals('admin', changes[0]['author'])
63+ self.assertEquals(1, changes[0]['version'])
64+ self.admin.wiki.deletePage('WikiOne')
65+ self.admin.wiki.deletePage('WikiTwo')
66+
67+ def test_getPageHTMLWithImage(self):
68+ # Create the wiki page (absolute image reference)
69+ self.admin.wiki.putPage('ImageTest',
70+ '[[Image(wiki:ImageTest:feed.png, nolink)]]\n', {})
71+ # Create attachment
72+ image_url = os.path.join(rpc_testenv.trac_src, 'trac',
73+ 'htdocs', 'feed.png')
74+ self.admin.wiki.putAttachmentEx('ImageTest', 'feed.png', 'test image',
75+ xmlrpclib.Binary(open(image_url, 'r').read()))
76+ # Check rendering absolute
77+ markup_1 = self.admin.wiki.getPageHTML('ImageTest')
78+ self.assertEquals('<html><body><p>\n<img src="http://127.0.0.1:8765'
79+ '/raw-attachment/wiki/ImageTest/feed.png" alt="test image" '
80+ 'title="test image" />\n</p>\n</body></html>', markup_1)
81+ # Change to relative image reference and check again
82+ self.admin.wiki.putPage('ImageTest',
83+ '[[Image(feed.png, nolink)]]\n', {})
84+ markup_2 = self.admin.wiki.getPageHTML('ImageTest')
85+ self.assertEquals(markup_2, markup_1)
86+
87+
88+def test_suite():
89+ return unittest.makeSuite(RpcWikiTestCase)
90+
91+if __name__ == '__main__':
92+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/xml_rpc.py.svn-base
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/.svn/text-base/xml_rpc.py.svn-base Mon May 30 19:44:56 2011 +0900
@@ -0,0 +1,115 @@
1+# -*- coding: utf-8 -*-
2+"""
3+License: BSD
4+
5+(c) 2009 ::: www.CodeResort.com - BV Network AS (simon-code@bvnetwork.no)
6+"""
7+
8+import unittest
9+
10+import xmlrpclib
11+
12+from tracrpc.tests import rpc_testenv, TracRpcTestCase
13+
14+class RpcXmlTestCase(TracRpcTestCase):
15+
16+ def setUp(self):
17+ TracRpcTestCase.setUp(self)
18+ self.anon = xmlrpclib.ServerProxy(rpc_testenv.url_anon)
19+ self.user = xmlrpclib.ServerProxy(rpc_testenv.url_user)
20+ self.admin = xmlrpclib.ServerProxy(rpc_testenv.url_admin)
21+
22+ def tearDown(self):
23+ TracRpcTestCase.tearDown(self)
24+
25+ def test_xmlrpc_permission(self):
26+ # Test returned response if not XML_RPC permission
27+ rpc_testenv._tracadmin('permission', 'remove', 'anonymous',
28+ 'XML_RPC', wait=True)
29+ e = self.assertRaises(xmlrpclib.Fault,
30+ self.anon.system.listMethods)
31+ self.assertEquals(403, e.faultCode)
32+ self.assertTrue('XML_RPC' in e.faultString)
33+ rpc_testenv._tracadmin('permission', 'add', 'anonymous',
34+ 'XML_RPC', wait=True)
35+
36+ def test_method_not_found(self):
37+ def local_test():
38+ self.admin.system.doesNotExist()
39+ self.fail("What? Method exists???")
40+ e = self.assertRaises(xmlrpclib.Fault, local_test)
41+ self.assertEquals(-32601, e.faultCode)
42+ self.assertTrue("not found" in e.faultString)
43+
44+ def test_wrong_argspec(self):
45+ def local_test():
46+ self.admin.system.listMethods("hello")
47+ self.fail("Oops. Wrong argspec accepted???")
48+ e = self.assertRaises(xmlrpclib.Fault, local_test)
49+ self.assertEquals(1, e.faultCode)
50+ self.assertTrue("listMethods() takes exactly 2 arguments" \
51+ in e.faultString)
52+
53+ def test_content_encoding(self):
54+ test_string = "øæåØÆÅàéüoö"
55+ # No encoding / encoding error
56+ def local_test():
57+ t_id = self.admin.ticket.create(test_string, test_string[::-1], {})
58+ self.admin.ticket.delete(t_id)
59+ self.fail("Expected ticket create to fail...")
60+ e = self.assertRaises(xmlrpclib.Fault, local_test)
61+ self.assertTrue(isinstance(e, xmlrpclib.Fault))
62+ self.assertEquals(-32700, e.faultCode)
63+ # Unicode version (encodable)
64+ from trac.util.text import to_unicode
65+ test_string = to_unicode(test_string)
66+ t_id = self.admin.ticket.create(test_string, test_string[::-1], {})
67+ self.assertTrue(t_id > 0)
68+ result = self.admin.ticket.get(t_id)
69+ self.assertEquals(result[0], t_id)
70+ self.assertEquals(result[3]['summary'], test_string)
71+ self.assertEquals(result[3]['description'], test_string[::-1])
72+ self.assertEquals(unicode, type(result[3]['summary']))
73+ self.admin.ticket.delete(t_id)
74+
75+ def test_to_and_from_datetime(self):
76+ from datetime import datetime
77+ from trac.util.datefmt import to_datetime, utc
78+ from tracrpc.xml_rpc import to_xmlrpc_datetime, from_xmlrpc_datetime
79+ now = to_datetime(None, utc)
80+ now_timetuple = now.timetuple()[:6]
81+ xmlrpc_now = to_xmlrpc_datetime(now)
82+ self.assertTrue(isinstance(xmlrpc_now, xmlrpclib.DateTime),
83+ "Expected xmlprc_now to be an xmlrpclib.DateTime")
84+ self.assertEquals(str(xmlrpc_now), now.strftime("%Y%m%dT%H:%M:%S"))
85+ now_from_xmlrpc = from_xmlrpc_datetime(xmlrpc_now)
86+ self.assertTrue(isinstance(now_from_xmlrpc, datetime),
87+ "Expected now_from_xmlrpc to be a datetime")
88+ self.assertEquals(now_from_xmlrpc.timetuple()[:6], now_timetuple)
89+ self.assertEquals(now_from_xmlrpc.tzinfo, utc)
90+
91+ def test_resource_not_found(self):
92+ # A Ticket resource
93+ e = self.assertRaises(xmlrpclib.Fault, self.admin.ticket.get, 2147483647)
94+ self.assertEquals(e.faultCode, 404)
95+ self.assertEquals(e.faultString,
96+ 'Ticket 2147483647 does not exist.')
97+ # A Wiki resource
98+ e = self.assertRaises(xmlrpclib.Fault, self.admin.wiki.getPage, "Test", 10)
99+ self.assertEquals(e.faultCode, 404)
100+ self.assertEquals(e.faultString,
101+ 'Wiki page "Test" does not exist at version 10')
102+
103+ def test_xml_encoding_special_characters(self):
104+ tid1 = self.admin.ticket.create(
105+ 'One & Two < Four', 'Desc & ription', {})
106+ ticket = self.admin.ticket.get(tid1)
107+ self.assertEquals('One & Two < Four', ticket[3]['summary'])
108+ self.assertEquals('Desc & ription', ticket[3]['description'])
109+ self.admin.ticket.delete(tid1)
110+
111+def test_suite():
112+ return unittest.makeSuite(RpcXmlTestCase)
113+
114+if __name__ == '__main__':
115+ unittest.main(defaultTest='test_suite')
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/ticket.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/tests/ticket.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/ticket.py Mon May 30 19:44:56 2011 +0900
@@ -129,6 +129,182 @@
129129 self.admin.ticket.delete(tid1)
130130 self.admin.ticket.delete(tid2)
131131
132+ def test_query_special_character_escape(self):
133+ # Note: This test only passes when using Trac 0.12+
134+ # See http://trac-hacks.org/ticket/7737
135+ if __import__('trac').__version__ < '0.12':
136+ self.fail("Known issue: Trac 0.11 does not handle escaped input properly.")
137+ summary = ["here&now", "maybe|later", "back\slash"]
138+ search = ["here\&now", "maybe\|later", "back\\slash"]
139+ tids = []
140+ for s in summary:
141+ tids.append(self.admin.ticket.create(s,
142+ "test_special_character_escape", {}))
143+ try:
144+ for i in range(0, 3):
145+ self.assertEquals([tids[i]],
146+ self.admin.ticket.query("summary=%s" % search[i]))
147+ self.assertEquals(tids.sort(),
148+ self.admin.ticket.query("summary=%s" % "|".join(search)).sort())
149+ finally:
150+ for tid in tids:
151+ self.admin.ticket.delete(tid)
152+
153+ def test_update_author(self):
154+ tid = self.admin.ticket.create("ticket_update_author", "one", {})
155+ self.admin.ticket.update(tid, 'comment1', {})
156+ time.sleep(1)
157+ self.admin.ticket.update(tid, 'comment2', {}, False, 'foo')
158+ time.sleep(1)
159+ self.user.ticket.update(tid, 'comment3', {}, False, 'should_be_rejected')
160+ changes = self.admin.ticket.changeLog(tid)
161+ self.assertEquals(3, len(changes))
162+ for when, who, what, cnum, comment, _tid in changes:
163+ self.assertTrue(comment in ('comment1', 'comment2', 'comment3'))
164+ if comment == 'comment1':
165+ self.assertEquals('admin', who)
166+ if comment == 'comment2':
167+ self.assertEquals('foo', who)
168+ if comment == 'comment3':
169+ self.assertEquals('user', who)
170+ self.admin.ticket.delete(tid)
171+
172+ def test_create_at_time(self):
173+ from trac.util.datefmt import to_datetime, utc
174+ now = to_datetime(None, utc)
175+ minus1 = now - datetime.timedelta(days=1)
176+ # create the tickets (user ticket will not be permitted to change time)
177+ one = self.admin.ticket.create("create_at_time1", "ok", {}, False,
178+ xmlrpclib.DateTime(minus1))
179+ two = self.user.ticket.create("create_at_time3", "ok", {}, False,
180+ xmlrpclib.DateTime(minus1))
181+ # get the tickets
182+ t1 = self.admin.ticket.get(one)
183+ t2 = self.admin.ticket.get(two)
184+ # check timestamps
185+ self.assertTrue(t1[1] < t2[1])
186+ self.admin.ticket.delete(one)
187+ self.admin.ticket.delete(two)
188+
189+ def test_update_at_time(self):
190+ from trac.util.datefmt import to_datetime, utc
191+ now = to_datetime(None, utc)
192+ minus1 = now - datetime.timedelta(hours=1)
193+ minus2 = now - datetime.timedelta(hours=2)
194+ tid = self.admin.ticket.create("ticket_update_at_time", "ok", {})
195+ self.admin.ticket.update(tid, 'one', {}, False, '', xmlrpclib.DateTime(minus2))
196+ self.admin.ticket.update(tid, 'two', {}, False, '', xmlrpclib.DateTime(minus1))
197+ self.user.ticket.update(tid, 'three', {}, False, '', xmlrpclib.DateTime(minus1))
198+ time.sleep(1)
199+ self.user.ticket.update(tid, 'four', {})
200+ changes = self.admin.ticket.changeLog(tid)
201+ self.assertEquals(4, len(changes))
202+ # quick test to make sure each is older than previous
203+ self.assertTrue(changes[0][0] < changes[1][0] < changes[2][0])
204+ # margin of 2 seconds for tests
205+ justnow = xmlrpclib.DateTime(now - datetime.timedelta(seconds=1))
206+ self.assertTrue(justnow <= changes[2][0])
207+ self.assertTrue(justnow <= changes[3][0])
208+ self.admin.ticket.delete(tid)
209+
210+ def test_update_non_existing(self):
211+ try:
212+ self.admin.ticket.update(3344, "a comment", {})
213+ self.fail("Allowed to update non-existing ticket???")
214+ self.admin.ticket.delete(3234)
215+ except Exception, e:
216+ self.assertTrue("Ticket 3344 does not exist." in str(e))
217+
218+ def test_update_basic(self):
219+ import time
220+ # Basic update check, no 'action' or 'time_changed'
221+ tid = self.admin.ticket.create('test_update_basic1', 'ieidnsj', {
222+ 'owner': 'osimons'})
223+ # old-style (deprecated)
224+ self.admin.ticket.update(tid, "comment1", {'component': 'component2'})
225+ self.assertEquals(2, len(self.admin.ticket.changeLog(tid)))
226+ # new-style with 'action'
227+ time.sleep(1) # avoid "columns ticket, time, field are not unique"
228+ self.admin.ticket.update(tid, "comment2", {'component': 'component1',
229+ 'action': 'leave'})
230+ self.assertEquals(4, len(self.admin.ticket.changeLog(tid)))
231+ self.admin.ticket.delete(tid)
232+
233+ def test_update_time_changed(self):
234+ # Update with collision check
235+ import datetime
236+ from tracrpc.xml_rpc import from_xmlrpc_datetime, to_xmlrpc_datetime
237+ tid = self.admin.ticket.create('test_update_time_changed', '...', {})
238+ tid, created, modified, attrs = self.admin.ticket.get(tid)
239+ then = from_xmlrpc_datetime(modified) - datetime.timedelta(minutes=1)
240+ # Unrestricted old-style update (to be removed soon)
241+ try:
242+ self.admin.ticket.update(tid, "comment1",
243+ {'_ts': to_xmlrpc_datetime(then)})
244+ except Exception, e:
245+ self.assertTrue("Ticket has been updated since last get" in str(e))
246+ # Update with 'action' to test new-style update.
247+ try:
248+ self.admin.ticket.update(tid, "comment1",
249+ {'_ts': to_xmlrpc_datetime(then),
250+ 'action': 'leave'})
251+ except Exception, e:
252+ self.assertTrue("modified by someone else" in str(e))
253+ self.admin.ticket.delete(tid)
254+
255+ def test_update_time_same(self):
256+ # Update with collision check
257+ import datetime
258+ from tracrpc.xml_rpc import from_xmlrpc_datetime, to_xmlrpc_datetime
259+
260+ # Unrestricted old-style update (to be removed soon)
261+ tid = self.admin.ticket.create('test_update_time_same', '...', {})
262+ tid, created, modified, attrs = self.admin.ticket.get(tid)
263+ ts = attrs['_ts']
264+ self.admin.ticket.update(tid, "comment1",
265+ {'_ts': ts})
266+ self.admin.ticket.delete(tid)
267+
268+ # Update with 'action' to test new-style update.
269+ tid = self.admin.ticket.create('test_update_time_same', '...', {})
270+ tid, created, modified, attrs = self.admin.ticket.get(tid)
271+ ts = attrs['_ts']
272+ self.admin.ticket.update(tid, "comment1",
273+ {'_ts': ts, 'action': 'leave'})
274+ self.admin.ticket.delete(tid)
275+
276+ def test_update_action(self):
277+ # Updating with 'action' in attributes
278+ tid = self.admin.ticket.create('test_update_action', 'ss')
279+ current = self.admin.ticket.get(tid)
280+ self.assertEqual('', current[3].get('owner', ''))
281+ updated = self.admin.ticket.update(tid, "comment1",
282+ {'action': 'reassign',
283+ 'action_reassign_reassign_owner': 'user'})
284+ self.assertEqual('user', updated[3].get('owner'))
285+ self.admin.ticket.delete(tid)
286+
287+ def test_update_action_non_existing(self):
288+ # Updating with non-existing 'action' in attributes
289+ tid = self.admin.ticket.create('test_update_action_wrong', 'ss')
290+ try:
291+ self.admin.ticket.update(tid, "comment1",
292+ {'action': 'reassign',
293+ 'action_reassign_reassign_owner': 'user'})
294+ except Exception, e:
295+ self.assertTrue("invalid action" in str(e))
296+ self.admin.ticket.delete(tid)
297+
298+ def test_update_field_non_existing(self):
299+ tid = self.admin.ticket.create('test_update_field_non_existing', 'yw3')
300+ try:
301+ self.admin.ticket.update(tid, "comment1",
302+ {'does_not_exist': 'eiwrjoer'})
303+ except Exception, e:
304+ self.assertTrue("no such column" in str(e))
305+ self.admin.ticket.delete(tid)
306+
307+
132308 class RpcTicketVersionTestCase(TracRpcTestCase):
133309
134310 def setUp(self):
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/wiki.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/tests/wiki.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/wiki.py Mon May 30 19:44:56 2011 +0900
@@ -64,6 +64,27 @@
6464 self.admin.wiki.deletePage('WikiOne')
6565 self.admin.wiki.deletePage('WikiTwo')
6666
67+ def test_getPageHTMLWithImage(self):
68+ # Create the wiki page (absolute image reference)
69+ self.admin.wiki.putPage('ImageTest',
70+ '[[Image(wiki:ImageTest:feed.png, nolink)]]\n', {})
71+ # Create attachment
72+ image_url = os.path.join(rpc_testenv.trac_src, 'trac',
73+ 'htdocs', 'feed.png')
74+ self.admin.wiki.putAttachmentEx('ImageTest', 'feed.png', 'test image',
75+ xmlrpclib.Binary(open(image_url, 'r').read()))
76+ # Check rendering absolute
77+ markup_1 = self.admin.wiki.getPageHTML('ImageTest')
78+ self.assertEquals('<html><body><p>\n<img src="http://127.0.0.1:8765'
79+ '/raw-attachment/wiki/ImageTest/feed.png" alt="test image" '
80+ 'title="test image" />\n</p>\n</body></html>', markup_1)
81+ # Change to relative image reference and check again
82+ self.admin.wiki.putPage('ImageTest',
83+ '[[Image(feed.png, nolink)]]\n', {})
84+ markup_2 = self.admin.wiki.getPageHTML('ImageTest')
85+ self.assertEquals(markup_2, markup_1)
86+
87+
6788 def test_suite():
6889 return unittest.makeSuite(RpcWikiTestCase)
6990
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/tests/xml_rpc.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/tests/xml_rpc.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/tests/xml_rpc.py Mon May 30 19:44:56 2011 +0900
@@ -100,6 +100,14 @@
100100 self.assertEquals(e.faultString,
101101 'Wiki page "Test" does not exist at version 10')
102102
103+ def test_xml_encoding_special_characters(self):
104+ tid1 = self.admin.ticket.create(
105+ 'One & Two < Four', 'Desc & ription', {})
106+ ticket = self.admin.ticket.get(tid1)
107+ self.assertEquals('One & Two < Four', ticket[3]['summary'])
108+ self.assertEquals('Desc & ription', ticket[3]['description'])
109+ self.admin.ticket.delete(tid1)
110+
103111 def test_suite():
104112 return unittest.makeSuite(RpcXmlTestCase)
105113
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/ticket.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/ticket.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/ticket.py Mon May 30 19:44:56 2011 +0900
@@ -43,8 +43,17 @@
4343 yield (None, ((list, int),), self.getAvailableActions)
4444 yield (None, ((list, int),), self.getActions)
4545 yield (None, ((list, int),), self.get)
46- yield ('TICKET_CREATE', ((int, str, str), (int, str, str, dict), (int, str, str, dict, bool)), self.create)
47- yield (None, ((list, int, str), (list, int, str, dict), (list, int, str, dict, bool)), self.update)
46+ yield ('TICKET_CREATE', ((int, str, str),
47+ (int, str, str, dict),
48+ (int, str, str, dict, bool),
49+ (int, str, str, dict, bool, datetime)),
50+ self.create)
51+ yield (None, ((list, int, str),
52+ (list, int, str, dict),
53+ (list, int, str, dict, bool),
54+ (list, int, str, dict, bool, str),
55+ (list, int, str, dict, bool, str, datetime)),
56+ self.update)
4857 yield (None, ((None, int),), self.delete)
4958 yield (None, ((dict, int), (dict, int, int)), self.changeLog)
5059 yield (None, ((list, int),), self.listAttachments)
@@ -58,7 +67,13 @@
5867
5968 # Exported methods
6069 def query(self, req, qstr='status!=closed'):
61- """ Perform a ticket query, returning a list of ticket ID's. """
70+ """
71+ Perform a ticket query, returning a list of ticket ID's.
72+ All queries will use stored settings for maximum number of results per
73+ page and paging options. Use `max=n` to define number of results to
74+ receive, and use `page=n` to page through larger result sets. Using
75+ `max=0` will turn off paging and return all results.
76+ """
6277 q = query.Query.from_string(self.env, qstr)
6378 ticket_realm = Resource('ticket')
6479 out = []
@@ -96,7 +111,7 @@
96111 t = model.Ticket(self.env, id)
97112 actions = []
98113 for action in ts.get_available_actions(req, t):
99- fragment = hints = genshi.builder.Fragment()
114+ fragment = genshi.builder.Fragment()
100115 hints = []
101116 first_label = None
102117 for controller in ts.action_controllers:
@@ -133,10 +148,12 @@
133148 """ Fetch a ticket. Returns [id, time_created, time_changed, attributes]. """
134149 t = model.Ticket(self.env, id)
135150 req.perm(t.resource).require('TICKET_VIEW')
151+ t['_ts'] = str(t.time_changed)
136152 return (t.id, t.time_created, t.time_changed, t.values)
137153
138- def create(self, req, summary, description, attributes = {}, notify=False):
139- """ Create a new ticket, returning the ticket ID. """
154+ def create(self, req, summary, description, attributes={}, notify=False, when=None):
155+ """ Create a new ticket, returning the ticket ID.
156+ Overriding 'when' requires admin permission. """
140157 t = model.Ticket(self.env)
141158 t['summary'] = summary
142159 t['description'] = description
@@ -145,7 +162,12 @@
145162 t[k] = v
146163 t['status'] = 'new'
147164 t['resolution'] = ''
148- t.insert()
165+ # custom create timestamp?
166+ if when and not 'TICKET_ADMIN' in req.perm:
167+ self.log.warn("RPC ticket.create: %r not allowed to create with "
168+ "non-current timestamp (%r)", req.authname, when)
169+ when = None
170+ t.insert(when=when)
149171 # Call ticket change listeners
150172 ts = TicketSystem(self.env)
151173 for listener in ts.change_listeners:
@@ -159,22 +181,47 @@
159181 "of ticket #%s: %s" % (t.id, e))
160182 return t.id
161183
162- def update(self, req, id, comment, attributes = {}, notify=False):
184+ def update(self, req, id, comment, attributes={}, notify=False, author='', when=None):
163185 """ Update a ticket, returning the new ticket in the same form as
164- getTicket(). Requires a valid 'action' in attributes to support workflow. """
165- now = to_datetime(None, utc)
186+ get(). 'New-style' call requires two additional items in attributes:
187+ (1) 'action' for workflow support (including any supporting fields
188+ as retrieved by getActions()),
189+ (2) '_ts' changetime token for detecting update collisions (as received
190+ from get() or update() calls).
191+ ''Calling update without 'action' and '_ts' changetime token is
192+ deprecated, and will raise errors in a future version.'' """
166193 t = model.Ticket(self.env, id)
194+ # custom author?
195+ if author and not (req.authname == 'anonymous' \
196+ or 'TICKET_ADMIN' in req.perm(t.resource)):
197+ # only allow custom author if anonymous is permitted or user is admin
198+ self.log.warn("RPC ticket.update: %r not allowed to change author "
199+ "to %r for comment on #%d", req.authname, author, id)
200+ author = ''
201+ author = author or req.authname
202+ # custom change timestamp?
203+ if when and not 'TICKET_ADMIN' in req.perm(t.resource):
204+ self.log.warn("RPC ticket.update: %r not allowed to update #%d with "
205+ "non-current timestamp (%r)", author, id, when)
206+ when = None
207+ when = when or to_datetime(None, utc)
208+ # and action...
167209 if not 'action' in attributes:
168210 # FIXME: Old, non-restricted update - remove soon!
169211 self.log.warning("Rpc ticket.update for ticket %d by user %s " \
170212 "has no workflow 'action'." % (id, req.authname))
171213 req.perm(t.resource).require('TICKET_MODIFY')
214+ time_changed = attributes.pop('_ts', None)
215+ if time_changed and str(time_changed) != str(t.time_changed):
216+ raise TracError("Ticket has been updated since last get().")
172217 for k, v in attributes.iteritems():
173218 t[k] = v
174- t.save_changes(req.authname, comment, when=now)
219+ t.save_changes(author, comment, when=when)
175220 else:
176221 ts = TicketSystem(self.env)
177222 tm = TicketModule(self.env)
223+ # TODO: Deprecate update without time_changed timestamp
224+ time_changed = str(attributes.pop('_ts', t.time_changed))
178225 action = attributes.get('action')
179226 avail_actions = ts.get_available_actions(req, t)
180227 if not action in avail_actions:
@@ -188,7 +235,7 @@
188235 # TicketModule reads req.args - need to move things there...
189236 req.args.update(attributes)
190237 req.args['comment'] = comment
191- req.args['ts'] = str(t.time_changed) # collision hack...
238+ req.args['ts'] = time_changed
192239 changes, problems = tm.get_ticket_changes(req, t, action)
193240 for warning in problems:
194241 add_warning(req, "Rpc ticket.update: %s" % warning)
@@ -199,17 +246,17 @@
199246 else:
200247 tm._apply_ticket_changes(t, changes)
201248 self.log.debug("Rpc ticket.update save: %s" % repr(t.values))
202- t.save_changes(req.authname, comment, when=now)
249+ t.save_changes(author, comment, when=when)
203250 # Apply workflow side-effects
204251 for controller in controllers:
205252 controller.apply_action_side_effects(req, t, action)
206253 # Call ticket change listeners
207254 for listener in ts.change_listeners:
208- listener.ticket_changed(t, comment, req.authname, t._old)
255+ listener.ticket_changed(t, comment, author, t._old)
209256 if notify:
210257 try:
211258 tn = TicketNotifyEmail(self.env)
212- tn.notify(t, newticket=False, modtime=now)
259+ tn.notify(t, newticket=False, modtime=when)
213260 except Exception, e:
214261 self.log.exception("Failure sending notification on change of "
215262 "ticket #%s: %s" % (t.id, e))
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/web_ui.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/web_ui.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/web_ui.py Mon May 30 19:44:56 2011 +0900
@@ -204,4 +204,5 @@
204204 def get_navigation_items(self, req):
205205 if req.perm.has_permission('XML_RPC'):
206206 yield ('metanav', 'rpc',
207- tag.a('RPC API', href=req.href.rpc(), accesskey=1))
207+ tag.a('API', href=req.href.rpc(), accesskey=1))
208+
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/wiki.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/wiki.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/wiki.py Mon May 30 19:44:56 2011 +0900
@@ -11,10 +11,11 @@
1111
1212 from trac.attachment import Attachment
1313 from trac.core import *
14+from trac.mimeview import Context
1415 from trac.resource import Resource, ResourceNotFound
1516 from trac.wiki.api import WikiSystem
1617 from trac.wiki.model import WikiPage
17-from trac.wiki.formatter import wiki_to_html
18+from trac.wiki.formatter import wiki_to_html, format_to_html
1819
1920 from tracrpc.api import IXMLRPCHandler, expose_rpc, Binary
2021 from tracrpc.util import StringIO, to_utimestamp, from_utimestamp
@@ -94,7 +95,9 @@
9495 def getPageHTML(self, req, pagename, version=None):
9596 """ Return page in rendered HTML, latest version. """
9697 text = self.getPage(req, pagename, version)
97- html = wiki_to_html(text, self.env, req, absurls=1)
98+ resource = Resource('wiki', pagename, version)
99+ context = Context.from_request(req, resource, absurls=True)
100+ html = format_to_html(self.env, context, text)
98101 return '<html><body>%s</body></html>' % html
99102
100103 def getAllPages(self, req):
diff -r c390a7ba79b4 -r 9be46ec8635a plugins/svn/xmlrpcplugin/tracrpc/xml_rpc.py
--- a/plugins/svn/xmlrpcplugin/tracrpc/xml_rpc.py Mon May 30 18:58:47 2011 +0900
+++ b/plugins/svn/xmlrpcplugin/tracrpc/xml_rpc.py Mon May 30 19:44:56 2011 +0900
@@ -17,6 +17,7 @@
1717 from trac.resource import ResourceNotFound
1818 from trac.util.datefmt import utc
1919 from trac.util.text import to_unicode
20+from trac.web.api import RequestDone
2021
2122 from tracrpc.api import XMLRPCSystem, IRPCProtocol, Binary, \
2223 RPCError, MethodNotFound, ProtocolException, ServiceException
@@ -146,6 +147,7 @@
146147 req.send_header('Content-Length', len(response))
147148 req.end_headers()
148149 req.write(response)
150+ raise RequestDone
149151
150152 def _normalize_xml_input(self, args):
151153 """ Normalizes arguments (at any level - traversing dicts and lists):
Show on old repository browser