Add (optional) validation for minimum anonymous torrent size (#342)

MINIMUM_ANONYMOUS_TORRENT_SIZE can be used to require a minimum total
size of torrents uploaded by anonymous users (ie. without accounts).

Sets up a "framework" for post-WTForm torrent validation as well;
this can easily be extended into filename blacklists and such.
This commit is contained in:
Anna-Maria Meriniemi 2017-08-20 03:48:08 +03:00 committed by Arylide
parent 48d4217f02
commit 39fcfc0058
4 changed files with 67 additions and 20 deletions

View File

@ -46,6 +46,9 @@ ENFORCE_MAIN_ANNOUNCE_URL = False
MAIN_ANNOUNCE_URL = 'http://127.0.0.1:6881/announce'
TRACKER_API_URL = 'http://127.0.0.1:6881/api'
TRACKER_API_AUTH = 'topsecret'
# Torrents uploaded without an account must be at least this big in total (bytes)
# Set to 0 to disable
MINIMUM_ANONYMOUS_TORRENT_SIZE = 1 * 1024 * 1024
BACKUP_TORRENT_FOLDER = 'torrents'

View File

@ -99,22 +99,25 @@ def v2_api_upload():
upload_form.category.choices = _create_upload_category_choices()
if upload_form.validate():
torrent = backend.handle_torrent_upload(upload_form, flask.g.user)
try:
torrent = backend.handle_torrent_upload(upload_form, flask.g.user)
# Create a response dict with relevant data
torrent_metadata = {
'url': flask.url_for('torrents.view', torrent_id=torrent.id, _external=True),
'id': torrent.id,
'name': torrent.display_name,
'hash': torrent.info_hash.hex(),
'magnet': torrent.magnet_uri
}
# Create a response dict with relevant data
torrent_metadata = {
'url': flask.url_for('torrents.view', torrent_id=torrent.id, _external=True),
'id': torrent.id,
'name': torrent.display_name,
'hash': torrent.info_hash.hex(),
'magnet': torrent.magnet_uri
}
return flask.jsonify(torrent_metadata)
else:
# Map errors back from form fields into the api keys
mapped_errors = {UPLOAD_API_FORM_KEYMAP.get(k, k): v for k, v in upload_form.errors.items()}
return flask.jsonify({'errors': mapped_errors}), 400
return flask.jsonify(torrent_metadata)
except backend.TorrentExtraValidationException:
pass
# Map errors back from form fields into the api keys
mapped_errors = {UPLOAD_API_FORM_KEYMAP.get(k, k): v for k, v in upload_form.errors.items()}
return flask.jsonify({'errors': mapped_errors}), 400
# #################################### TEMPORARY ####################################

View File

@ -15,6 +15,11 @@ from nyaa.extensions import db
app = flask.current_app
class TorrentExtraValidationException(Exception):
def __init__(self, errors):
self.errors = errors
@utils.cached_function
def get_category_id_map():
''' Reads database for categories and turns them into a dict with
@ -44,7 +49,37 @@ def _replace_utf8_values(dict_or_list):
return did_change
def validate_torrent_post_upload(torrent, upload_form=None):
''' Validates a Torrent instance before it's saved to the database.
Enforcing user-and-such-based validations is more flexible here vs WTForm context '''
errors = {
'torrent_file': []
}
# Encorce minimum size for userless uploads
minimum_anonymous_torrent_size = app.config['MINIMUM_ANONYMOUS_TORRENT_SIZE']
if torrent.user is None and torrent.filesize < minimum_anonymous_torrent_size:
errors['torrent_file'].append('Torrent too small for an anonymous uploader')
print(errors)
# Remove keys with empty lists
errors = {k: v for k, v in errors.items() if v}
if errors:
if upload_form:
# Add error messages to the form fields
for field_name, field_errors in errors.items():
getattr(upload_form, field_name).errors.extend(field_errors)
# Clear out the wtforms dict to force a regeneration
upload_form._errors = None
raise TorrentExtraValidationException(errors)
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
# Delete exisiting torrent which is marked as deleted
@ -197,6 +232,9 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
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)
db.session.commit()
# Store the actual torrent file as well

View File

@ -307,13 +307,16 @@ def upload():
upload_form.category.choices = _create_upload_category_choices()
if flask.request.method == 'POST' and upload_form.validate():
torrent = backend.handle_torrent_upload(upload_form, flask.g.user)
try:
torrent = backend.handle_torrent_upload(upload_form, flask.g.user)
return flask.redirect(flask.url_for('torrents.view', torrent_id=torrent.id))
else:
# If we get here with a POST, it means the form data was invalid: return a non-okay status
status_code = 400 if flask.request.method == 'POST' else 200
return flask.render_template('upload.html', upload_form=upload_form), status_code
return flask.redirect(flask.url_for('torrents.view', torrent_id=torrent.id))
except backend.TorrentExtraValidationException:
pass
# If we get here with a POST, it means the form data was invalid: return a non-okay status
status_code = 400 if flask.request.method == 'POST' else 200
return flask.render_template('upload.html', upload_form=upload_form), status_code
@cached_function