From da090fd3f5e9a12373e94bdf4b211d84609a012f Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 22 Jun 2024 13:22:37 +0100 Subject: [PATCH] Normalize line endings in form submissions Fixes #506 --- app/blueprints/admin/email.py | 4 ++-- app/blueprints/admin/languageseditor.py | 4 ++-- app/blueprints/admin/tagseditor.py | 4 ++-- app/blueprints/admin/warningseditor.py | 4 ++-- app/blueprints/collections/__init__.py | 4 ++-- app/blueprints/packages/packages.py | 5 +++-- app/blueprints/packages/reviews.py | 4 ++-- app/blueprints/report/__init__.py | 4 ++-- app/blueprints/threads/__init__.py | 10 +++++----- app/utils/__init__.py | 7 +++++++ 10 files changed, 29 insertions(+), 21 deletions(-) diff --git a/app/blueprints/admin/email.py b/app/blueprints/admin/email.py index 84772535..dbda208c 100644 --- a/app/blueprints/admin/email.py +++ b/app/blueprints/admin/email.py @@ -22,14 +22,14 @@ from app.markdown import render_markdown from app.tasks.emails import send_user_email, send_bulk_email as task_send_bulk -from app.utils import rank_required, add_audit_log +from app.utils import rank_required, add_audit_log, normalize_line_endings from . import bp from app.models import UserRank, User, AuditSeverity class SendEmailForm(FlaskForm): subject = StringField("Subject", [InputRequired(), Length(1, 300)]) - text = TextAreaField("Message", [InputRequired()]) + text = TextAreaField("Message", [InputRequired()], filters=[normalize_line_endings]) submit = SubmitField("Send") diff --git a/app/blueprints/admin/languageseditor.py b/app/blueprints/admin/languageseditor.py index a101c133..6a285261 100644 --- a/app/blueprints/admin/languageseditor.py +++ b/app/blueprints/admin/languageseditor.py @@ -22,7 +22,7 @@ from wtforms.validators import InputRequired, Length, Optional from app.models import db, AuditSeverity, UserRank, Language, Package, PackageState, PackageTranslation -from app.utils import add_audit_log, rank_required +from app.utils import add_audit_log, rank_required, normalize_line_endings from . import bp @@ -38,7 +38,7 @@ def language_list(): class LanguageForm(FlaskForm): id = StringField("Id", [InputRequired(), Length(2, 10)]) - title = TextAreaField("Title", [Optional(), Length(2, 100)]) + title = TextAreaField("Title", [Optional(), Length(2, 100)], filters=[normalize_line_endings]) submit = SubmitField("Save") diff --git a/app/blueprints/admin/tagseditor.py b/app/blueprints/admin/tagseditor.py index 77454597..91816373 100644 --- a/app/blueprints/admin/tagseditor.py +++ b/app/blueprints/admin/tagseditor.py @@ -23,7 +23,7 @@ from . import bp from app.models import Permission, Tag, db, AuditSeverity -from app.utils import add_audit_log +from app.utils import add_audit_log, normalize_line_endings @bp.route("/tags/") @@ -44,7 +44,7 @@ def tag_list(): class TagForm(FlaskForm): title = StringField("Title", [InputRequired(), Length(3, 100)]) - description = TextAreaField("Description", [Optional(), Length(0, 500)]) + description = TextAreaField("Description", [Optional(), Length(0, 500)], filters=[normalize_line_endings]) name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")]) submit = SubmitField("Save") diff --git a/app/blueprints/admin/warningseditor.py b/app/blueprints/admin/warningseditor.py index 397c661e..f19a0e7d 100644 --- a/app/blueprints/admin/warningseditor.py +++ b/app/blueprints/admin/warningseditor.py @@ -20,7 +20,7 @@ from wtforms import StringField, TextAreaField, SubmitField from wtforms.validators import InputRequired, Length, Optional, Regexp -from app.utils import rank_required +from app.utils import rank_required, normalize_line_endings from . import bp from app.models import UserRank, ContentWarning, db @@ -33,7 +33,7 @@ def warning_list(): class WarningForm(FlaskForm): title = StringField("Title", [InputRequired(), Length(3, 100)]) - description = TextAreaField("Description", [Optional(), Length(0, 500)]) + description = TextAreaField("Description", [Optional(), Length(0, 500)], filters=[normalize_line_endings]) name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")]) submit = SubmitField("Save") diff --git a/app/blueprints/collections/__init__.py b/app/blueprints/collections/__init__.py index 7a0c56ff..d19befe8 100644 --- a/app/blueprints/collections/__init__.py +++ b/app/blueprints/collections/__init__.py @@ -25,7 +25,7 @@ from wtforms.validators import InputRequired, Length, Optional, Regexp from app.models import Collection, db, Package, Permission, CollectionPackage, User, UserRank, AuditSeverity -from app.utils import nonempty_or_none +from app.utils import nonempty_or_none, normalize_line_endings from app.utils.models import is_package_page, add_audit_log, create_session bp = Blueprint("collections", __name__) @@ -78,7 +78,7 @@ class CollectionForm(FlaskForm): name = StringField("URL", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")]) short_description = StringField(lazy_gettext("Short Description"), [Optional(), Length(0, 200)]) - long_description = TextAreaField(lazy_gettext("Page Content"), [Optional()], filters=[nonempty_or_none]) + long_description = TextAreaField(lazy_gettext("Page Content"), [Optional()], filters=[nonempty_or_none, normalize_line_endings]) private = BooleanField(lazy_gettext("Private")) pinned = BooleanField(lazy_gettext("Pinned to my profile")) descriptions = FieldList( diff --git a/app/blueprints/packages/packages.py b/app/blueprints/packages/packages.py index 870ce6eb..f03605d1 100644 --- a/app/blueprints/packages/packages.py +++ b/app/blueprints/packages/packages.py @@ -44,7 +44,8 @@ PackageScreenshot, NotificationType, AuditLogEntry, PackageAlias, PackageProvides, PackageGameSupport, \ PackageDailyStats, Collection from app.utils import is_user_bot, get_int_or_abort, is_package_page, abs_url_for, add_audit_log, get_package_by_info, \ - add_notification, get_system_user, rank_required, get_games_from_csv, get_daterange_options, post_to_approval_thread + add_notification, get_system_user, rank_required, get_games_from_csv, get_daterange_options, \ + post_to_approval_thread, normalize_line_endings from app.logic.package_approval import validate_package_for_approval, can_move_to_state from app.logic.game_support import game_support_set @@ -238,7 +239,7 @@ class PackageForm(FlaskForm): license = QuerySelectField(lazy_gettext("License"), [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name) media_license = QuerySelectField(lazy_gettext("Media License"), [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name) - desc = TextAreaField(lazy_gettext("Long Description (Markdown)"), [Optional(), Length(0,10000)]) + desc = TextAreaField(lazy_gettext("Long Description (Markdown)"), [Optional(), Length(0,10000)], filters=[normalize_line_endings]) repo = StringField(lazy_gettext("VCS Repository URL"), [Optional(), URL()], filters = [lambda x: x or None]) website = StringField(lazy_gettext("Website URL"), [Optional(), URL()], filters = [lambda x: x or None]) diff --git a/app/blueprints/packages/reviews.py b/app/blueprints/packages/reviews.py index 29a52890..ad4f678b 100644 --- a/app/blueprints/packages/reviews.py +++ b/app/blueprints/packages/reviews.py @@ -29,7 +29,7 @@ Permission, AuditSeverity, PackageState, Language from app.tasks.webhooktasks import post_discord_webhook from app.utils import is_package_page, add_notification, get_int_or_abort, is_yes, is_safe_url, rank_required, \ - add_audit_log, has_blocked_domains, should_return_json + add_audit_log, has_blocked_domains, should_return_json, normalize_line_endings from . import bp @@ -57,7 +57,7 @@ class ReviewForm(FlaskForm): get_pk=lambda a: a.id, get_label=lambda a: a.title, default=get_default_language) - comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)]) + comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)], filters=[normalize_line_endings]) rating = RadioField(lazy_gettext("Rating"), [InputRequired()], choices=[("5", lazy_gettext("Yes")), ("3", lazy_gettext("Neutral")), ("1", lazy_gettext("No"))]) btn_submit = SubmitField(lazy_gettext("Save")) diff --git a/app/blueprints/report/__init__.py b/app/blueprints/report/__init__.py index f0a05b2f..dcc22f04 100644 --- a/app/blueprints/report/__init__.py +++ b/app/blueprints/report/__init__.py @@ -25,13 +25,13 @@ from app.models import User, UserRank from app.tasks.emails import send_user_email from app.tasks.webhooktasks import post_discord_webhook -from app.utils import is_no, abs_url_samesite +from app.utils import is_no, abs_url_samesite, normalize_line_endings bp = Blueprint("report", __name__) class ReportForm(FlaskForm): - message = TextAreaField(lazy_gettext("Message"), [InputRequired(), Length(10, 10000)]) + message = TextAreaField(lazy_gettext("Message"), [InputRequired(), Length(10, 10000)], filters=[normalize_line_endings]) submit = SubmitField(lazy_gettext("Report")) diff --git a/app/blueprints/threads/__init__.py b/app/blueprints/threads/__init__.py index 39185eea..8fdcdbb5 100644 --- a/app/blueprints/threads/__init__.py +++ b/app/blueprints/threads/__init__.py @@ -16,8 +16,7 @@ from flask import Blueprint, request, render_template, abort, flash, redirect, url_for from flask_babel import gettext, lazy_gettext -from sqlalchemy import or_ -from sqlalchemy.orm import selectinload, joinedload +from sqlalchemy.orm import selectinload from app.markdown import get_user_mentions, render_markdown from app.tasks.webhooktasks import post_discord_webhook @@ -27,7 +26,8 @@ from flask_login import current_user, login_required from app.models import Package, db, User, Permission, Thread, UserRank, AuditSeverity, \ NotificationType, ThreadReply -from app.utils import add_notification, is_yes, add_audit_log, get_system_user, has_blocked_domains +from app.utils import add_notification, is_yes, add_audit_log, get_system_user, has_blocked_domains, \ + normalize_line_endings from flask_wtf import FlaskForm from wtforms import StringField, TextAreaField, SubmitField, BooleanField from wtforms.validators import InputRequired, Length @@ -178,7 +178,7 @@ def delete_reply(id): class CommentForm(FlaskForm): - comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(2, 2000)]) + comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(2, 2000)], filters=[normalize_line_endings]) btn_submit = SubmitField(lazy_gettext("Comment")) @@ -279,7 +279,7 @@ def view(id): class ThreadForm(FlaskForm): title = StringField(lazy_gettext("Title"), [InputRequired(), Length(3,100)]) - comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)]) + comment = TextAreaField(lazy_gettext("Comment"), [InputRequired(), Length(10, 2000)], filters=[normalize_line_endings]) private = BooleanField(lazy_gettext("Private")) btn_submit = SubmitField(lazy_gettext("Open Thread")) diff --git a/app/utils/__init__.py b/app/utils/__init__.py index 69fdc575..38adc3c0 100644 --- a/app/utils/__init__.py +++ b/app/utils/__init__.py @@ -52,6 +52,13 @@ def nonempty_or_none(str): return str +def normalize_line_endings(value: Optional[str]) -> Optional[str]: + if value is None: + return None + + return value.replace("\r\n", "\n").strip() + "\n" + + def should_return_json(): return "application/json" in request.accept_mimetypes and \ not "text/html" in request.accept_mimetypes