Ticket #12603: prepareticket.py

File prepareticket.py, 6.1 KB (added by michael2402, 10 years ago)
Line 
1from trac.core import *
2from trac.web import IRequestHandler, IRequestFilter
3from trac.web.session import DetachedSession
4from trac.ticket.web_ui import TicketModule
5from base64 import b64decode, urlsafe_b64decode
6import time, zlib, sys
7from xml.sax import saxutils
8
9class AnonymousDetachedSession(DetachedSession):
10 """Allows access to non-authenticated session storage."""
11
12 def __init__(self, env, sid):
13 super(AnonymousDetachedSession, self).__init__(env, None)
14 self.get_session(sid)
15
16class JosmTicket(Component):
17 """ This class handles calls to '/josmticket' to create a josm specific ticket.
18 This can also be used to create a ticket from inside JOSM.
19
20 It allows you to send in debug data as text (base64 or gzipped) or to store them for later retrival with a retrival key.
21
22 Author of pdata-storage: Michael Zangl with help of stoecker
23 """
24
25 handlers = ExtensionPoint(IRequestHandler)
26
27 implements(IRequestHandler)
28
29 def match_request(self, req):
30 return req.path_info == '/josmticket'
31
32 def process_request(self, req):
33 self.cleanup_pdata()
34
35 # can only be /josmticket to store pdata
36 pdata = req.args.get('pdata')
37 try:
38 pdata = b64decode(pdata);
39 except TypeError:
40 self.send_josm_error(req, "Could not decode base64")
41 return
42
43 if len(pdata) > 100000:
44 self.send_josm_error(req, "Cannot store that much text")
45 return
46
47 req.session['preparedticket'] = pdata;
48 req.session['preparedticket_time'] = int(time.time());
49 self.send_josm_ticket(req, {'preparedid': req.session.sid})
50
51 def send_josm_error(self, req, message):
52 self.send_josm_ticket(req, {'error': message}, 'error')
53
54 def send_josm_ticket(self, req, data, status = 'ok'):
55 items = [" <%(k)s>%(v)s</%(k)s>\n" % {'k': saxutils.escape(key), 'v' : saxutils.escape(value)} for key, value in data.items()]
56 req.send('<?xml version="1.0" encoding="UTF-8"?>\n'
57 + '<josmticket status="ok">\n'
58 + ''.join(items)
59 + '</josmticket>', 'text/xml', 200 if status == 'ok' else 400)
60
61
62 def process_base64_data(self, data, urlsafe = True):
63 lens = len(data)
64 lenx = int(lens) / 4 * 4 # round down
65 try:
66 func = urlsafe_b64decode if urlsafe else b64decode
67 return func(unicode(data[:lenx]).encode('ascii'))
68 except:
69 return "(Error decoding base64)"
70
71 def process_tdata(self, data):
72 data = "==== What steps will reproduce the problem?\n" \
73 + "1. \n" \
74 + "2. \n" \
75 + "3. \n" \
76 + "\n" \
77 + "==== What is the expected result?\n\n" \
78 + "==== What happens instead?\n\n" \
79 + "==== Please provide any additional information below. Attach a screenshot if possible.\n\n" \
80 + "{{{\n" + str(data) + "\n}}}\n"
81 return data
82
83 implements(IRequestFilter)
84 def pre_process_request(self, req, handler):
85 if(req.path_info == '/josmticket'):
86 if req.method == 'POST' :
87 if req.args.getfirst('pdata') != None:
88 #pdata store request.
89 # Let us handle this, convince trac that this is not a CSRF attack
90 req.form_token = 'x'
91 req.args['__FORM_TOKEN'] = 'x'
92 return handler;
93 req.environ['REQUEST_METHOD'] = 'GET'
94
95 description = self.get_description_from_args(req)
96
97 # This also seems to fix the unicode issues.
98 req.args['description'] = self.process_tdata(description).decode('utf-8', errors='ignore')
99 req.args['keywords'] = 'template_report'
100 req.environ['PATH_INFO'] = '/newticket'
101 for newhandler in self.handlers:
102 if isinstance(newhandler, TicketModule):
103 handler = newhandler
104 return (handler)
105
106 def get_description_from_args(self, req):
107 gdata = req.args.getfirst('gdata')
108 if gdata != None:
109 gdata = self.process_base64_data(gdata)
110 try:
111 return zlib.decompressobj(15+32).decompress(gdata)
112 except:
113 return "Error decompressing compressed data."
114
115 tdata = req.args.getfirst('tdata')
116 if tdata != None:
117 tdata = self.process_base64_data(tdata)
118 return tdata
119
120 data = req.args.getfirst('data')
121 if data != None:
122 return self.process_base64_data(data, False)
123
124 sid = req.args.getfirst('pdata_stored')
125 if sid != None:
126 return self.get_pdata(sid);
127 return ""
128
129 def get_pdata(self, sessionId):
130 session = AnonymousDetachedSession(self.env, sessionId)
131 return session.get('preparedticket', 'Could not retrieve text.')
132
133 def post_process_request(self, req, *args):
134 return args
135
136 # Clean pdata older than a given time.
137 def cleanup_pdata(self):
138 # This code ignores possible race condition
139 CLEAN_PERIOD = 60 * 60 #1h
140 t = int(time.time())
141 try:
142 last_clean = self.env.db_query("SELECT value FROM system "
143 "WHERE name='pdata_lastclean' AND value >= %s", (t - CLEAN_PERIOD,))
144 if len(last_clean) > 0:
145 return;
146 except:
147 pass
148
149 self.do_cleanup_pdata()
150 with self.env.db_transaction as db:
151 db("INSERT OR REPLACE INTO system VALUES ('pdata_lastclean', %s)", (t,))
152
153 def do_cleanup_pdata(self):
154 DELETE_AFTER = 6 * 60 * 60 #6h
155 etime = int(time.time() - DELETE_AFTER)
156 with self.env.db_transaction as db:
157 to_clean = db("SELECT sid FROM session_attribute WHERE name = 'preparedticket_time' AND " + db.cast('value', 'int') + " < %s", (etime,));
158 to_clean = list(to_clean) # fetch them, just to be sure.
159 for sid, in to_clean:
160 db("""
161 DELETE FROM session_attribute
162 WHERE (name = 'preparedticket' OR name = 'preparedticket_time') AND sid = %s""",
163 (str(sid),))
164