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.
  • [get | view] (2013-04-03 06:55:50, 9.9 KB) [[attachment:AttachTorrent.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.