diff --git a/aurweb/pkgbase/validate.py b/aurweb/pkgbase/validate.py index 3c50e578..b76e1a38 100644 --- a/aurweb/pkgbase/validate.py +++ b/aurweb/pkgbase/validate.py @@ -1,6 +1,9 @@ +from http import HTTPStatus from typing import Any -from aurweb import db +from fastapi import HTTPException + +from aurweb import config, db from aurweb.exceptions import ValidationError from aurweb.models import PackageBase @@ -12,8 +15,8 @@ def request( merge_into: str, context: dict[str, Any], ) -> None: - if not comments: - raise ValidationError(["The comment field must not be empty."]) + # validate comment + comment(comments) if type == "merge": # Perform merge-related checks. @@ -32,3 +35,21 @@ def request( if target.ID == pkgbase.ID: # TODO: This error needs to be translated. raise ValidationError(["You cannot merge a package base into itself."]) + + +def comment(comment: str): + if not comment: + raise ValidationError(["The comment field must not be empty."]) + + if len(comment) > config.getint("options", "max_chars_comment", 5000): + raise ValidationError(["Maximum number of characters for comment exceeded."]) + + +def comment_raise_http_ex(comments: str): + try: + comment(comments) + except ValidationError as err: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=err.data[0], + ) diff --git a/aurweb/routers/pkgbase.py b/aurweb/routers/pkgbase.py index a91b2b48..213348cd 100644 --- a/aurweb/routers/pkgbase.py +++ b/aurweb/routers/pkgbase.py @@ -159,6 +159,8 @@ async def pkgbase_flag_post( request, "pkgbase/flag.html", context, status_code=HTTPStatus.BAD_REQUEST ) + validate.comment_raise_http_ex(comments) + has_cred = request.user.has_credential(creds.PKGBASE_FLAG) if has_cred and not pkgbase.OutOfDateTS: now = time.utcnow() @@ -185,8 +187,7 @@ async def pkgbase_comments_post( """Add a new comment via POST request.""" pkgbase = get_pkg_or_base(name, PackageBase) - if not comment: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST) + validate.comment_raise_http_ex(comment) # If the provided comment is different than the record's version, # update the db record. @@ -304,9 +305,9 @@ async def pkgbase_comment_post( pkgbase = get_pkg_or_base(name, PackageBase) db_comment = get_pkgbase_comment(pkgbase, id) - if not comment: - raise HTTPException(status_code=HTTPStatus.BAD_REQUEST) - elif request.user.ID != db_comment.UsersID: + validate.comment_raise_http_ex(comment) + + if request.user.ID != db_comment.UsersID: raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED) # If the provided comment is different than the record's version, @@ -602,6 +603,9 @@ async def pkgbase_disown_post( ): pkgbase = get_pkg_or_base(name, PackageBase) + if comments: + validate.comment_raise_http_ex(comments) + comaints = {c.User for c in pkgbase.comaintainers} approved = [pkgbase.Maintainer] + list(comaints) has_cred = request.user.has_credential(creds.PKGBASE_DISOWN, approved=approved) @@ -873,6 +877,7 @@ async def pkgbase_delete_post( ) if comments: + validate.comment_raise_http_ex(comments) # Update any existing deletion requests' ClosureComment. with db.begin(): requests = pkgbase.requests.filter( @@ -966,6 +971,9 @@ async def pkgbase_merge_post( request, "pkgbase/merge.html", context, status_code=HTTPStatus.BAD_REQUEST ) + if comments: + validate.comment_raise_http_ex(comments) + with db.begin(): update_closure_comment(pkgbase, MERGE_ID, comments, target=target) diff --git a/aurweb/templates.py b/aurweb/templates.py index 9e18014c..51b9d342 100644 --- a/aurweb/templates.py +++ b/aurweb/templates.py @@ -70,6 +70,7 @@ def make_context(request: Request, title: str, next: str = None): commit_url = aurweb.config.get_with_fallback("devel", "commit_url", None) commit_hash = aurweb.config.get_with_fallback("devel", "commit_hash", None) + max_chars_comment = aurweb.config.getint("options", "max_chars_comment", 5000) if commit_hash: # Shorten commit_hash to a short Git hash. commit_hash = commit_hash[:7] @@ -92,6 +93,7 @@ def make_context(request: Request, title: str, next: str = None): "creds": aurweb.auth.creds, "next": next if next else request.url.path, "version": os.environ.get("COMMIT_HASH", aurweb.config.AURWEB_VERSION), + "max_chars_comment": max_chars_comment, } diff --git a/conf/config.defaults b/conf/config.defaults index db885b65..9b3023d7 100644 --- a/conf/config.defaults +++ b/conf/config.defaults @@ -49,6 +49,8 @@ salt_rounds = 12 redis_address = redis://localhost ; Toggles traceback display in templates/errors/500.html. traceback = 0 +; Maximum number of characters for a comment +max_chars_comment = 5000 [ratelimit] request_limit = 4000 diff --git a/po/aurweb.pot b/po/aurweb.pot index 88f9de5d..b1a467e4 100644 --- a/po/aurweb.pot +++ b/po/aurweb.pot @@ -2371,3 +2371,7 @@ msgid "Note that if you hide your email address, it'll " "receive an email. However, replies are typically sent to the " "mailing-list and would then be visible in the archive." msgstr "" + +#: templates/partials/packages/comment_form.html +msgid "Maximum number of characters" +msgstr "" diff --git a/templates/partials/packages/comment_form.html b/templates/partials/packages/comment_form.html index bf9a14d0..cecc496e 100644 --- a/templates/partials/packages/comment_form.html +++ b/templates/partials/packages/comment_form.html @@ -21,12 +21,15 @@ Routes: | format('', "") | safe }} +
+ {{ "Maximum number of characters" | tr }}: {{ max_chars_comment }}.

diff --git a/templates/pkgbase/comments/edit.html b/templates/pkgbase/comments/edit.html index f938287e..0d4ec28a 100644 --- a/templates/pkgbase/comments/edit.html +++ b/templates/pkgbase/comments/edit.html @@ -24,13 +24,17 @@ "" ) | safe }} +
+ {{ "Maximum number of characters" | tr }}: {{ max_chars_comment }}.

+ rows="10" + maxlength="{{ max_chars_comment }}" + >{{ comment.Comments }}

diff --git a/templates/pkgbase/delete.html b/templates/pkgbase/delete.html index 5bd74b72..defcf58f 100644 --- a/templates/pkgbase/delete.html +++ b/templates/pkgbase/delete.html @@ -50,7 +50,7 @@

diff --git a/templates/pkgbase/disown.html b/templates/pkgbase/disown.html index 1aedde4f..c01398c8 100644 --- a/templates/pkgbase/disown.html +++ b/templates/pkgbase/disown.html @@ -55,6 +55,7 @@

diff --git a/templates/pkgbase/flag.html b/templates/pkgbase/flag.html index 0335cf18..11d33029 100644 --- a/templates/pkgbase/flag.html +++ b/templates/pkgbase/flag.html @@ -60,7 +60,8 @@ + cols="50" + maxlength="{{ max_chars_comment }}">

diff --git a/templates/pkgbase/merge.html b/templates/pkgbase/merge.html index 981bd649..4b32da87 100644 --- a/templates/pkgbase/merge.html +++ b/templates/pkgbase/merge.html @@ -52,6 +52,7 @@

diff --git a/templates/pkgbase/request.html b/templates/pkgbase/request.html index 3ffa2d2d..413146f0 100644 --- a/templates/pkgbase/request.html +++ b/templates/pkgbase/request.html @@ -64,7 +64,10 @@

+ rows="5" cols="50" + maxlength="{{ max_chars_comment }}" + >{{ comments or '' }} +

diff --git a/templates/requests/close.html b/templates/requests/close.html index df767eae..ee177811 100644 --- a/templates/requests/close.html +++ b/templates/requests/close.html @@ -26,7 +26,8 @@

+ rows="5" cols="50" maxlength="{{ max_chars_comment }}" + >

diff --git a/test/test_pkgbase_routes.py b/test/test_pkgbase_routes.py index a413fe8a..522bb68b 100644 --- a/test/test_pkgbase_routes.py +++ b/test/test_pkgbase_routes.py @@ -6,7 +6,7 @@ import pytest from fastapi.testclient import TestClient from sqlalchemy import and_ -from aurweb import asgi, db, time +from aurweb import asgi, config, db, time from aurweb.models.account_type import USER_ID, AccountType from aurweb.models.dependency_type import DependencyType from aurweb.models.package import Package @@ -25,6 +25,8 @@ from aurweb.testing.email import Email from aurweb.testing.html import get_errors, get_successes, parse_root from aurweb.testing.requests import Request +max_chars_comment = config.getint("options", "max_chars_comment", 5000) + def package_endpoint(package: Package) -> str: return f"/packages/{package.Name}" @@ -572,6 +574,38 @@ def test_pkgbase_comments( assert "form" in data +def test_pkgbase_comment_exceed_character_limit( + client: TestClient, + user: User, + package: Package, + comment: PackageComment, +): + # Post new comment + cookies = {"AURSID": user.login(Request(), "testPassword")} + pkgbasename = package.PackageBase.Name + endpoint = f"/pkgbase/{pkgbasename}/comments" + + with client as request: + request.cookies = cookies + resp = request.post( + endpoint, + data={"comment": "x" * (max_chars_comment + 1)}, + ) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + assert "Maximum number of characters for comment exceeded." in resp.text + # Edit existing + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + request.cookies = cookies + endp = f"/pkgbase/{pkgbasename}/comments/{comment.ID}" + response = request.post( + endp, + data={"comment": "x" * (max_chars_comment + 1)}, + ) + assert response.status_code == HTTPStatus.BAD_REQUEST + assert "Maximum number of characters for comment exceeded." in resp.text + + def test_pkgbase_comment_edit_unauthorized( client: TestClient, user: User, @@ -935,6 +969,28 @@ def test_pkgbase_request_post_no_comment_error( assert error.text.strip() == expected +def test_pkgbase_request_post_comment_exceed_character_limit( + client: TestClient, user: User, package: Package +): + endpoint = f"/pkgbase/{package.PackageBase.Name}/request" + cookies = {"AURSID": user.login(Request(), "testPassword")} + with client as request: + request.cookies = cookies + resp = request.post( + endpoint, + data={ + "type": "deletion", + "comments": "x" * (max_chars_comment + 1), + }, + ) + assert resp.status_code == int(HTTPStatus.OK) + + root = parse_root(resp.text) + error = root.xpath('//ul[@class="errorlist"]/li')[0] + expected = "Maximum number of characters for comment exceeded." + assert error.text.strip() == expected + + def test_pkgbase_request_post_merge_not_found_error( client: TestClient, user: User, package: Package ): @@ -1087,6 +1143,13 @@ def test_pkgbase_flag( assert resp.status_code == int(HTTPStatus.SEE_OTHER) assert pkgbase.Flagger is None + # Try flagging with a comment that exceeds our character limit. + with client as request: + request.cookies = cookies + data = {"comments": "x" * (max_chars_comment + 1)} + resp = request.post(f"/pkgbase/{pkgbase.Name}/flag", data=data) + assert resp.status_code == int(HTTPStatus.BAD_REQUEST) + # Flag it again. with client as request: request.cookies = cookies