Attachment 'AttachTorrent.py'
Download 1 import datetime
2 import hashlib
3 import os
4 import time
5 import urlparse
6 from cStringIO import StringIO
7
8 from werkzeug import http_date
9 from MoinMoin import wikiutil, config, log
10 from MoinMoin.Page import Page
11 from MoinMoin.action.AttachFile import getFilename, remove_attachment, upload_form
12 import bencode
13 import pymysql
14
15 __author__ = 'murfen'
16
17 logging = log.getLogger(__name__)
18
19 ## cache MySQL connection to xbt tracker database
20 db = None
21 torrent_pass_private_key = None
22
23 def execute(pagename, request):
24 _ = request.getText
25 do = request.values.get('do', 'get')
26 handler = globals().get('_do_%s' % do)
27 if handler:
28 msg = handler(pagename, request)
29 else:
30 msg = _('Unsupported GetTorrent sub-action: %s') % do
31 if msg:
32 error_msg(pagename, request, msg)
33
34 def error_msg(pagename, request, msg):
35 msg = wikiutil.escape(msg)
36 request.theme.add_msg(msg, "error")
37 Page(request, pagename).send_page()
38
39 def _access_file(pagename, request):
40 """ Check form parameter `target` and return a tuple of
41 `(pagename, filename, filepath)` for an existing attachment.
42
43 Return `(pagename, None, None)` if an error occurs.
44 This is an exact copy from AttachFile standard module
45 """
46 _ = request.getText
47
48 error = None
49 if not request.values.get('target'):
50 error = _("Filename of attachment not specified!")
51 else:
52 filename = wikiutil.taintfilename(request.values['target'])
53 fpath = getFilename(request, pagename, filename)
54
55 if os.path.isfile(fpath):
56 return (pagename, filename, fpath)
57 error = _("Attachment '%(filename)s' does not exist!") % {'filename': filename}
58
59 error_msg(pagename, request, error)
60 return (pagename, None, None)
61
62 def _do_get(pagename,request):
63 """
64 The function is almost the same as in the AttachFile module
65 Additionally it filters the file through process_torrent()
66 """
67 _ = request.getText
68
69 pagename, filename, fpath = _access_file(pagename, request)
70 if not request.user.may.read(pagename):
71 return _('You are not allowed to get attachments from this page.')
72 if not filename:
73 return # error msg already sent in _access_file
74 # request.user.xbt_id = 1
75 # request.user.save()
76
77 timestamp = datetime.datetime.fromtimestamp(os.path.getmtime(fpath))
78 if_modified = request.if_modified_since
79 if if_modified and if_modified >= timestamp:
80 request.status_code = 304
81 else:
82 mt = wikiutil.MimeType(filename=filename)
83 content_type = mt.content_type()
84 mime_type = mt.mime_type() # should be 'application/x-bittorrent'
85
86 # There is no solution that is compatible to IE except stripping non-ascii chars
87 filename_enc = filename.encode(config.charset)
88
89 # for dangerous files (like .html), when we are in danger of cross-site-scripting attacks,
90 # we just let the user store them to disk ('attachment').
91 # For safe files, we directly show them inline (this also works better for IE).
92 dangerous = mime_type in request.cfg.mimetypes_xss_protect
93 content_dispo = dangerous and 'attachment' or 'inline'
94
95 now = time.time()
96 request.headers['Date'] = http_date(now)
97 request.headers['Content-Type'] = content_type
98 request.headers['Last-Modified'] = http_date(timestamp)
99 request.headers['Expires'] = http_date(now - 365 * 24 * 3600)
100 # request.headers['Content-Length'] = os.path.getsize(fpath)
101 content_dispo_string = '%s; filename="%s"' % (content_dispo, filename_enc)
102 request.headers['Content-Disposition'] = content_dispo_string
103
104 # Process Torrent and send data
105 process_torrent(pagename,request,fpath)
106
107 def _do_del(pagename, request):
108 _ = request.getText
109
110 # change request.action to work around the ticket checking issue
111 action = request.action
112 request.action = 'AttachFile'
113 if not wikiutil.checkTicket(request, request.args.get('ticket', '')):
114 return _('Please use the interactive user interface to use action %(actionname)s!') % {'actionname': 'AttachFile.del' }
115 request.action = action
116
117 pagename, filename, fpath = _access_file(pagename, request)
118 if not request.user.may.delete(pagename):
119 return _('You are not allowed to delete attachments on this page.')
120 if not filename:
121 return # error msg already sent in _access_file
122 # remove torrent from the DB
123 remove_torrent(pagename, request, fpath)
124 remove_attachment(request, pagename, filename)
125
126 upload_form(pagename, request, msg=_("Attachment '%(filename)s' deleted.") % {'filename': filename})
127
128
129 def process_torrent(pagename, request, fpath):
130 _ = request.getText
131
132 if not db_connect(pagename, request):
133 return
134 try:
135 with open(fpath, 'rb') as f:
136 buf = f.read()
137 except :
138 error_msg(pagename, request, _("Cannot open the attachment"))
139 return
140 try:
141 bt = bencode.bdecode(buf)
142 except:
143 error_msg(pagename, request, _("Bad torrent file"))
144 return
145 # force private flag
146 try:
147 xbt_force_private = request.cfg.xbt_force_private
148 except AttributeError:
149 xbt_force_private = False
150 if xbt_force_private:
151 bt['info']['private'] = 1
152 info_hash_obj = hashlib.sha1(bencode.bencode(bt['info']))
153
154 # get xbt_users record
155 user_record = _get_xbt_user(pagename,request)
156 if not user_record:
157 return
158 uid, torrent_pass, torrent_pass_version, downloaded, uploaded = user_record
159
160 # write the new file to xbt_files table
161 sha = info_hash_obj.hexdigest()
162 c = db.cursor()
163 c.execute("SELECT flags FROM xbt_files WHERE info_hash=UNHEX(%s)", (sha,))
164 flag = c.fetchone()
165 if flag is None:
166 c.execute("INSERT INTO xbt_files(info_hash, mtime, ctime) VALUES(UNHEX(%s), UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
167 (sha,))
168 db.commit()
169 elif flag[0] % 2 :
170 error_msg(pagename, request, _("Torrent is marked for deletion!"))
171 return
172
173 # get a secret key from xbt_config
174 global torrent_pass_private_key
175 if not torrent_pass_private_key:
176 c.execute("select value from xbt_config where name = 'torrent_pass_private_key'")
177 try:
178 torrent_pass_private_key = c.fetchone()[0]
179 except TypeError:
180 logging.error("Cannot get torrent_pass_private_key from xbt_config table")
181 error_msg(pagename,request,_("Tracker configuration error"))
182 return
183
184 # compose the torrent password
185 s = "%s %d %d %s" % (torrent_pass_private_key, torrent_pass_version, uid, info_hash_obj.digest())
186 sha = hashlib.sha1(s).hexdigest()[0:24]
187 pwd = "%08x%s" % (uid, sha)
188
189 # get xbt tracker host:port setting
190 try:
191 xbt_host = request.cfg.xbt_host
192 except AttributeError:
193 logging.error("Tracker host parameter xbt_host is not set in wiki config")
194 error_msg(pagename, request, _("Wiki configuration error"))
195 return
196
197 # set the proper URL in the torrent file
198 bt['announce'] = 'http://%s/%s/announce' % (xbt_host, pwd)
199 # remove other trackers
200 if bt.has_key('announce-list'):
201 del bt['announce-list']
202 # update comment to current page full address
203 url_parts = urlparse.urlsplit(request.url)
204 comment = urlparse.urlunsplit((url_parts.scheme, url_parts.netloc, request.page.url(request), '', ''))
205 bt['comment'] = comment.encode('utf-8')
206 buf = bencode.bencode(bt)
207 request.headers['Content-Length'] = len(buf)
208
209 request.send_file(StringIO(buf))
210
211 def remove_torrent(pagename, request, fpath):
212 _ = request.getText
213
214 if not db_connect(pagename, request):
215 return
216 try:
217 with open(fpath, 'rb') as f:
218 buf = f.read()
219 bt = bencode.bdecode(buf)
220 except:
221 return
222 # force private flag
223 try:
224 xbt_force_private = request.cfg.xbt_force_private
225 except AttributeError:
226 xbt_force_private = False
227 if xbt_force_private:
228 bt['info']['private'] = 1
229 info_hash = hashlib.sha1(bencode.bencode(bt['info'])).hexdigest()
230 c = db.cursor()
231 try:
232 c.execute("update xbt_files set flags = 1 where info_hash = UNHEX(%s)", (info_hash, ))
233 db.commit()
234 except :
235 pass # ignore all database errors
236
237 def db_connect(pagename, request):
238 _ = request.getText
239
240 global db
241 if not db:
242 try:
243 db = pymysql.connect(**request.cfg.xbt_mysqldb)
244 except (AttributeError, pymysql.DatabaseError),err:
245 logging.error("Tracker DB connection error: %s" % err)
246 error_msg(pagename, request, _("Cannot connect to the tracker database"))
247 return False
248 else:
249 logging.debug("Greate! Reusing MySQL DB connection!")
250 return True
251
252
253 def _get_xbt_user(pagename, request):
254 """
255 Get xbt_users table fields for current user identified by torrent_pass=request.user.id
256 Create a new xbt_users record if it does not exists.
257 returned fields are: (uid, torrent_pass, torrent_pass_version, downloaded, uploaded)
258 """
259 _ = request.getText
260 if not request.user.valid or not request.user.name:
261 error_msg(pagename, request, _("Anonymous user is not allowed to get a torrent"))
262 return False
263
264 c = db.cursor()
265 c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",
266 (request.user.id,)) #rowcount =0
267 rec = c.fetchone()
268 if not rec:
269 # insert a new user
270 c.execute("INSERT INTO xbt_users(torrent_pass) VALUES(%s)", (request.user.id))
271 c.execute("SELECT uid, torrent_pass, torrent_pass_version, downloaded, uploaded FROM xbt_users WHERE torrent_pass = %s",
272 (request.user.id,)) #rowcount =0
273 rec = c.fetchone()
274 db.commit()
275 try:
276 uid = int(request.user.xbt_id)
277 except AttributeError:
278 uid = None
279 if not uid or uid != rec[0]:
280 request.user.xbt_id = rec[0]
281 request.user.save()
282 return rec
Attached Files
To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.You are not allowed to attach a file to this page.