Beispiel #1
0
def _delete_torrent(torrent, form, banform):
    editor = flask.g.user
    uploader = torrent.user

    # Only allow admins edit deleted torrents
    if torrent.deleted and not (editor and editor.is_moderator):
        flask.abort(404)

    action = None
    url = flask.url_for('main.home')

    ban_torrent = form.ban.data
    if banform:
        ban_torrent = ban_torrent or banform.ban_user.data or banform.ban_userip.data

    if form.delete.data and not torrent.deleted:
        action = 'deleted'
        torrent.deleted = True
        db.session.add(torrent)

    elif ban_torrent and not torrent.banned and editor.is_moderator:
        action = 'banned'
        torrent.banned = True
        if not torrent.deleted:
            torrent.deleted = True
            action = 'deleted and banned'
        db.session.add(models.TrackerApi(torrent.info_hash, 'remove'))
        torrent.stats.seed_count = 0
        torrent.stats.leech_count = 0
        db.session.add(torrent)

    elif form.undelete.data and torrent.deleted:
        action = 'undeleted'
        torrent.deleted = False
        if torrent.banned:
            action = 'undeleted and unbanned'
            torrent.banned = False
            db.session.add(models.TrackerApi(torrent.info_hash, 'insert'))
        db.session.add(torrent)

    elif form.unban.data and torrent.banned:
        action = 'unbanned'
        torrent.banned = False
        db.session.add(models.TrackerApi(torrent.info_hash, 'insert'))
        db.session.add(torrent)

    if not action and not ban_torrent:
        flask.flash(flask.Markup('What the f**k are you doing?'), 'danger')
        return flask.redirect(
            flask.url_for('torrents.edit', torrent_id=torrent.id))

    if action and editor.is_moderator:
        url = flask.url_for('torrents.view', torrent_id=torrent.id)
        if editor is not uploader:
            log = "Torrent [#{0}]({1}) has been {2}".format(
                torrent.id, url, action)
            adminlog = models.AdminLog(log=log, admin_id=editor.id)
            db.session.add(adminlog)

    if action:
        db.session.commit()
        flask.flash(
            flask.Markup('Torrent has been successfully {0}.'.format(action)),
            'success')

    if not banform or not (banform.ban_user.data or banform.ban_userip.data):
        return flask.redirect(url)

    if banform.ban_userip.data:
        tbanned = models.Ban.banned(None, torrent.uploader_ip).first()
        ubanned = True
        if uploader:
            ubanned = models.Ban.banned(None, uploader.last_login_ip).first()
        ipbanned = (tbanned and ubanned)

    if (banform.ban_user.data and (not uploader or uploader.is_banned)) or \
            (banform.ban_userip.data and ipbanned):
        flask.flash(flask.Markup('What the f**k are you doing?'), 'danger')
        return flask.redirect(
            flask.url_for('torrents.edit', torrent_id=torrent.id))

    flavor = "Nyaa" if app.config['SITE_FLAVOR'] == 'nyaa' else "Sukebei"
    eurl = flask.url_for('torrents.view',
                         torrent_id=torrent.id,
                         _external=True)
    reason = "[{0}#{1}]({2}) {3}".format(flavor, torrent.id, eurl,
                                         banform.reason.data)
    ban1 = models.Ban(admin_id=editor.id, reason=reason)
    ban2 = models.Ban(admin_id=editor.id, reason=reason)
    db.session.add(ban1)

    if uploader:
        uploader.status = models.UserStatusType.BANNED
        db.session.add(uploader)
        ban1.user_id = uploader.id
        ban2.user_id = uploader.id

    if banform.ban_userip.data:
        if not ubanned:
            ban1.user_ip = ip_address(uploader.last_login_ip)
            if not tbanned:
                uploader_ip = ip_address(torrent.uploader_ip)
                if ban1.user_ip != uploader_ip:
                    ban2.user_ip = uploader_ip
                    db.session.add(ban2)
        else:
            ban1.user_ip = ip_address(torrent.uploader_ip)

    uploader_str = "Anonymous"
    if uploader:
        uploader_url = flask.url_for('users.view_user',
                                     user_name=uploader.username)
        uploader_str = "[{0}]({1})".format(uploader.username, uploader_url)
    if ban1.user_ip:
        uploader_str += " IP({0})".format(ban1.user_ip)
        ban1.user_ip = ban1.user_ip.packed
    if ban2.user_ip:
        uploader_str += " IP({0})".format(ban2.user_ip)
        ban2.user_ip = ban2.user_ip.packed

    log = "Uploader {0} of torrent [#{1}]({2}) has been banned.".format(
        uploader_str, torrent.id,
        flask.url_for('torrents.view', torrent_id=torrent.id), action)
    adminlog = models.AdminLog(log=log, admin_id=editor.id)
    db.session.add(adminlog)

    db.session.commit()

    flask.flash(flask.Markup('Uploader has been successfully banned.'),
                'success')

    return flask.redirect(url)
Beispiel #2
0
def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
    ''' Stores a torrent to the database.
        May throw TorrentExtraValidationException if the form/torrent fails
        post-WTForm validation! Exception messages will also be added to their
        relevant fields on the given form. '''
    torrent_data = upload_form.torrent_file.parsed_data

    # Anonymous uploaders and non-trusted uploaders
    no_or_new_account = (
        not uploading_user
        or (uploading_user.age < app.config['RATELIMIT_ACCOUNT_AGE']
            and not uploading_user.is_trusted))

    if app.config['RATELIMIT_UPLOADS'] and no_or_new_account:
        now, torrent_count, next_time = check_uploader_ratelimit(
            uploading_user)
        if next_time > now:
            # This will flag the dialog in upload.html red and tell API users what's wrong
            upload_form.ratelimit.errors = [
                "You've gone over the upload ratelimit."
            ]
            raise TorrentExtraValidationException()

    if not uploading_user:
        if app.config['RAID_MODE_LIMIT_UPLOADS']:
            # XXX TODO: rename rangebanned to something more generic
            upload_form.rangebanned.errors = [
                app.config['RAID_MODE_UPLOADS_MESSAGE']
            ]
            raise TorrentExtraValidationException()
        elif models.RangeBan.is_rangebanned(
                ip_address(flask.request.remote_addr).packed):
            upload_form.rangebanned.errors = [
                "Your IP is banned from "
                "uploading anonymously."
            ]
            raise TorrentExtraValidationException()

    # Delete existing torrent which is marked as deleted
    if torrent_data.db_id is not None:
        old_torrent = models.Torrent.by_id(torrent_data.db_id)
        db.session.delete(old_torrent)
        db.session.commit()
        # Delete physical file after transaction has been committed
        _delete_info_dict(old_torrent)

    # The torrent has been  validated and is safe to access with ['foo'] etc - all relevant
    # keys and values have been checked for (see UploadForm in forms.py for details)
    info_dict = torrent_data.torrent_dict['info']

    changed_to_utf8 = _replace_utf8_values(torrent_data.torrent_dict)

    # Use uploader-given name or grab it from the torrent
    display_name = upload_form.display_name.data.strip(
    ) or info_dict['name'].decode('utf8').strip()
    information = (upload_form.information.data or '').strip()
    description = (upload_form.description.data or '').strip()

    # Sanitize fields
    display_name = sanitize_string(display_name)
    information = sanitize_string(information)
    description = sanitize_string(description)

    torrent_filesize = info_dict.get('length') or sum(
        f['length'] for f in info_dict.get('files'))

    # In case no encoding, assume UTF-8.
    torrent_encoding = torrent_data.torrent_dict.get('encoding',
                                                     b'utf-8').decode('utf-8')

    torrent = models.Torrent(id=torrent_data.db_id,
                             info_hash=torrent_data.info_hash,
                             display_name=display_name,
                             torrent_name=torrent_data.filename,
                             information=information,
                             description=description,
                             encoding=torrent_encoding,
                             filesize=torrent_filesize,
                             user=uploading_user,
                             uploader_ip=ip_address(
                                 flask.request.remote_addr).packed)

    # Store bencoded info_dict
    info_dict_path = torrent.info_dict_path

    info_dict_dir = os.path.dirname(info_dict_path)
    os.makedirs(info_dict_dir, exist_ok=True)

    with open(info_dict_path, 'wb') as out_file:
        out_file.write(torrent_data.bencoded_info_dict)

    torrent.stats = models.Statistic()
    torrent.has_torrent = True

    # Fields with default value will be None before first commit, so set .flags
    torrent.flags = 0

    torrent.anonymous = upload_form.is_anonymous.data if uploading_user else True
    torrent.hidden = upload_form.is_hidden.data
    torrent.remake = upload_form.is_remake.data
    torrent.complete = upload_form.is_complete.data
    # Copy trusted status from user if possible
    can_mark_trusted = uploading_user and uploading_user.is_trusted
    # To do, automatically mark trusted if user is trusted unless user specifies otherwise
    torrent.trusted = upload_form.is_trusted.data if can_mark_trusted else False

    # Only allow mods to upload locked torrents
    can_mark_locked = uploading_user and uploading_user.is_moderator
    torrent.comment_locked = upload_form.is_comment_locked.data if can_mark_locked else False

    # Set category ids
    torrent.main_category_id, torrent.sub_category_id = \
        upload_form.category.parsed_data.get_category_ids()

    # To simplify parsing the filelist, turn single-file torrent into a list
    torrent_filelist = info_dict.get('files')

    used_path_encoding = changed_to_utf8 and 'utf-8' or torrent_encoding

    parsed_file_tree = dict()
    if not torrent_filelist:
        # If single-file, the root will be the file-tree (no directory)
        file_tree_root = parsed_file_tree
        torrent_filelist = [{
            'length': torrent_filesize,
            'path': [info_dict['name']]
        }]
    else:
        # If multi-file, use the directory name as root for files
        file_tree_root = parsed_file_tree.setdefault(
            info_dict['name'].decode(used_path_encoding), {})

    # Parse file dicts into a tree
    for file_dict in torrent_filelist:
        # Decode path parts from utf8-bytes
        path_parts = [
            path_part.decode(used_path_encoding)
            for path_part in file_dict['path']
        ]

        filename = path_parts.pop()
        current_directory = file_tree_root

        for directory in path_parts:
            current_directory = current_directory.setdefault(directory, {})

        # Don't add empty filenames (BitComet directory)
        if filename:
            current_directory[filename] = file_dict['length']

    parsed_file_tree = utils.sorted_pathdict(parsed_file_tree)

    json_bytes = json.dumps(parsed_file_tree,
                            separators=(',', ':')).encode('utf8')
    torrent.filelist = models.TorrentFilelist(filelist_blob=json_bytes)

    db.session.add(torrent)
    db.session.flush()

    # Store the users trackers
    trackers = OrderedSet()
    announce = torrent_data.torrent_dict.get('announce', b'').decode('ascii')
    if announce:
        trackers.add(announce)

    # List of lists with single item
    announce_list = torrent_data.torrent_dict.get('announce-list', [])
    for announce in announce_list:
        trackers.add(announce[0].decode('ascii'))

    # Store webseeds
    # qBittorrent doesn't omit url-list but sets it as '' even when there are no webseeds
    webseed_list = torrent_data.torrent_dict.get('url-list') or []
    if isinstance(webseed_list, bytes):
        webseed_list = [webseed_list
                        ]  # qB doesn't contain a sole url in a list
    webseeds = OrderedSet(webseed.decode('utf-8') for webseed in webseed_list)

    # Remove our trackers, maybe? TODO ?

    # Search for/Add trackers in DB
    db_trackers = OrderedSet()
    for announce in trackers:
        tracker = models.Trackers.by_uri(announce)

        # Insert new tracker if not found
        if not tracker:
            tracker = models.Trackers(uri=announce)
            db.session.add(tracker)
            db.session.flush()
        elif tracker.is_webseed:
            # If we have an announce marked webseed (user error, malicy?), reset it.
            # Better to have "bad" announces than "hiding" proper announces in webseeds/url-list.
            tracker.is_webseed = False
            db.session.flush()

        db_trackers.add(tracker)

    # Same for webseeds
    for webseed_url in webseeds:
        webseed = models.Trackers.by_uri(webseed_url)

        if not webseed:
            webseed = models.Trackers(uri=webseed_url, is_webseed=True)
            db.session.add(webseed)
            db.session.flush()

        # Don't add trackers into webseeds
        if webseed.is_webseed:
            db_trackers.add(webseed)

    # Store tracker refs in DB
    for order, tracker in enumerate(db_trackers):
        torrent_tracker = models.TorrentTrackers(torrent_id=torrent.id,
                                                 tracker_id=tracker.id,
                                                 order=order)
        db.session.add(torrent_tracker)

    # Before final commit, validate the torrent again
    validate_torrent_post_upload(torrent, upload_form)

    # Add to tracker whitelist
    db.session.add(models.TrackerApi(torrent.info_hash, 'insert'))

    db.session.commit()

    # Store the actual torrent file as well
    torrent_file = upload_form.torrent_file.data
    if app.config.get('BACKUP_TORRENT_FOLDER'):
        torrent_file.seek(0, 0)

        torrent_dir = app.config['BACKUP_TORRENT_FOLDER']
        os.makedirs(torrent_dir, exist_ok=True)

        torrent_path = os.path.join(
            torrent_dir,
            '{}.{}'.format(torrent.id, secure_filename(torrent_file.filename)))
        torrent_file.save(torrent_path)
    torrent_file.close()

    return torrent