diff --git a/securedrop/models.py b/securedrop/models.py index 402c979ed1..38e07a9e47 100644 --- a/securedrop/models.py +++ b/securedrop/models.py @@ -184,6 +184,8 @@ def to_json(self): class Submission(db.Model): + MAX_MESSAGE_LEN = 100000 + __tablename__ = 'submissions' id = Column(Integer, primary_key=True) uuid = Column(String(36), unique=True, nullable=False) diff --git a/securedrop/source_app/forms.py b/securedrop/source_app/forms.py index 353de62af4..715fbddb40 100644 --- a/securedrop/source_app/forms.py +++ b/securedrop/source_app/forms.py @@ -1,9 +1,10 @@ +from flask import current_app from flask_babel import lazy_gettext as gettext from flask_wtf import FlaskForm -from wtforms import PasswordField -from wtforms.validators import InputRequired, Regexp, Length +from wtforms import FileField, PasswordField, TextAreaField +from wtforms.validators import InputRequired, Regexp, Length, ValidationError -from models import Source +from models import Source, Submission class LoginForm(FlaskForm): @@ -17,3 +18,20 @@ class LoginForm(FlaskForm): # Make sure to allow dashes since some words in the wordlist have them Regexp(r'[\sA-Za-z0-9-]+$', message=gettext('Invalid input.')) ]) + + +class SubmissionForm(FlaskForm): + msg = TextAreaField("msg", render_kw={"placeholder": gettext("Write a message.")}) + fh = FileField("fh") + + def validate_msg(self, field): + if len(field.data) > Submission.MAX_MESSAGE_LEN: + message = gettext("Message text too long.") + if current_app.instance_config.allow_document_uploads: + message = "{} {}".format( + message, + gettext( + "Large blocks of text must be uploaded as a file, not copied and pasted." + ) + ) + raise ValidationError(message) diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py index 3b787c56b3..172849fbc6 100644 --- a/securedrop/source_app/main.py +++ b/securedrop/source_app/main.py @@ -17,7 +17,7 @@ from source_app.utils import (logged_in, generate_unique_codename, async_genkey, normalize_timestamps, valid_codename, get_entropy_estimate) -from source_app.forms import LoginForm +from source_app.forms import LoginForm, SubmissionForm def make_blueprint(config): @@ -145,13 +145,21 @@ def lookup(): replies=replies, flagged=g.source.flagged, new_user=session.get('new_user', None), - haskey=current_app.crypto_util.get_fingerprint( - g.filesystem_id)) + haskey=current_app.crypto_util.get_fingerprint(g.filesystem_id), + form=SubmissionForm(), + ) @view.route('/submit', methods=('POST',)) @login_required def submit(): allow_document_uploads = current_app.instance_config.allow_document_uploads + form = SubmissionForm() + if not form.validate(): + for field, errors in form.errors.items(): + for error in errors: + flash(error, "error") + return redirect(url_for('main.lookup')) + msg = request.form['msg'] fh = None if allow_document_uploads and 'fh' in request.files: @@ -190,21 +198,23 @@ def submit(): fh.stream)) if first_submission: - msg = render_template('first_submission_flashed_message.html') - flash(Markup(msg), "success") + flash_message = render_template('first_submission_flashed_message.html') + flash(Markup(flash_message), "success") else: if msg and not fh: html_contents = gettext('Thanks! We received your message.') - elif not msg and fh: + elif fh and not msg: html_contents = gettext('Thanks! We received your document.') else: html_contents = gettext('Thanks! We received your message and ' 'document.') - msg = render_template('next_submission_flashed_message.html', - html_contents=html_contents) - flash(Markup(msg), "success") + flash_message = render_template( + 'next_submission_flashed_message.html', + html_contents=html_contents + ) + flash(Markup(flash_message), "success") new_submissions = [] for fname in fnames: diff --git a/securedrop/source_templates/lookup.html b/securedrop/source_templates/lookup.html index ee9fc810d3..b9bf6fb933 100644 --- a/securedrop/source_templates/lookup.html +++ b/securedrop/source_templates/lookup.html @@ -54,12 +54,12 @@

{{ gettext('Submit Messages') }}

{% if allow_document_uploads %}
- + {{ form.fh() }}

{{ gettext('Maximum upload size: 500 MB') }}

{% endif %}
- + {{ form.msg(class="fill-parent") }}
diff --git a/securedrop/tests/test_source.py b/securedrop/tests/test_source.py index fa6c4b1b56..0313a6790f 100644 --- a/securedrop/tests/test_source.py +++ b/securedrop/tests/test_source.py @@ -352,11 +352,9 @@ def test_submit_empty_message(source_app): def test_submit_big_message(source_app): - ''' - When the message is larger than 512KB it's written to disk instead of - just residing in memory. Make sure the different return type of - SecureTemporaryFile is handled as well as BytesIO. - ''' + """ + Test the message size limit. + """ with source_app.test_client() as app: new_codename(app, session) _dummy_submission(app) @@ -366,7 +364,7 @@ def test_submit_big_message(source_app): follow_redirects=True) assert resp.status_code == 200 text = resp.data.decode('utf-8') - assert "Thanks! We received your message" in text + assert "Message text too long." in text def test_submit_file(source_app):