Post validation: better length validation for post submissions
This commit is contained in:
parent
f2d657811f
commit
6b53867ae1
3 changed files with 41 additions and 29 deletions
|
@ -1,6 +1,5 @@
|
||||||
import time
|
import time
|
||||||
import gevent
|
import gevent
|
||||||
import requests
|
|
||||||
from files.helpers.wrappers import *
|
from files.helpers.wrappers import *
|
||||||
from files.helpers.sanitize import *
|
from files.helpers.sanitize import *
|
||||||
from files.helpers.alerts import *
|
from files.helpers.alerts import *
|
||||||
|
@ -36,6 +35,24 @@ discounts = {
|
||||||
|
|
||||||
titleheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"}
|
titleheaders = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36"}
|
||||||
|
|
||||||
|
MAX_TITLE_LENGTH = 500
|
||||||
|
MAX_URL_LENGTH = 2048
|
||||||
|
MAX_BODY_LENGTH = 20000
|
||||||
|
|
||||||
|
# Get request value `val` and ensure it is within length constraints
|
||||||
|
# : Returns an either tuple (good_value, error)
|
||||||
|
# : TODO it may make sense to do more sanitisation here
|
||||||
|
def guarded_value(val, min_len, max_len):
|
||||||
|
raw = request.values.get(val, '').strip()
|
||||||
|
raw = raw.replace('\u200e', '')
|
||||||
|
|
||||||
|
if len(raw) < min_len:
|
||||||
|
return (None, ({"error": f"Minimum length for {val} is {min_len}"}, 403))
|
||||||
|
|
||||||
|
if len(raw) > max_len:
|
||||||
|
return (None, ({"error": f"Maximum length for {val} is {max_len}"}, 403))
|
||||||
|
|
||||||
|
return (raw, None)
|
||||||
|
|
||||||
@app.post("/toggle_club/<pid>")
|
@app.post("/toggle_club/<pid>")
|
||||||
@auth_required
|
@auth_required
|
||||||
|
@ -425,6 +442,7 @@ def morecomments(v, cid):
|
||||||
|
|
||||||
return render_template("comments.html", v=v, comments=comments, p=p, render_replies=True, ajax=True)
|
return render_template("comments.html", v=v, comments=comments, p=p, render_replies=True, ajax=True)
|
||||||
|
|
||||||
|
|
||||||
@app.post("/edit_post/<pid>")
|
@app.post("/edit_post/<pid>")
|
||||||
@limiter.limit("1/second;30/minute;200/hour;1000/day")
|
@limiter.limit("1/second;30/minute;200/hour;1000/day")
|
||||||
@auth_required
|
@auth_required
|
||||||
|
@ -433,11 +451,11 @@ def edit_post(pid, v):
|
||||||
|
|
||||||
if p.author_id != v.id and not (v.admin_level > 1 and v.admin_level > 2): abort(403)
|
if p.author_id != v.id and not (v.admin_level > 1 and v.admin_level > 2): abort(403)
|
||||||
|
|
||||||
title = request.values.get("title", "").strip().replace('','')
|
title, err = guarded_value("title", 1, MAX_TITLE_LENGTH)
|
||||||
|
if err: return err
|
||||||
|
|
||||||
body = request.values.get("body", "").strip().replace('','')
|
body, err = guarded_value("body", 0, MAX_BODY_LENGTH)
|
||||||
|
if err: return err
|
||||||
if len(body) > 20000: return {"error":"Character limit is 20000!"}, 403
|
|
||||||
|
|
||||||
if v.id == p.author_id:
|
if v.id == p.author_id:
|
||||||
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
|
if v.longpost and (len(body) < 280 or ' [](' in body or body.startswith('[](')):
|
||||||
|
@ -446,8 +464,8 @@ def edit_post(pid, v):
|
||||||
return {"error":"You have to type less than 140 characters!"}, 403
|
return {"error":"You have to type less than 140 characters!"}, 403
|
||||||
|
|
||||||
if title != p.title:
|
if title != p.title:
|
||||||
|
p.title = title
|
||||||
title_html = filter_emojis_only(title, edit=True)
|
title_html = filter_emojis_only(title, edit=True)
|
||||||
p.title = title[:500]
|
|
||||||
p.title_html = title_html
|
p.title_html = title_html
|
||||||
|
|
||||||
if request.files.get("file") and request.headers.get("cf-ipcountry") != "T1":
|
if request.files.get("file") and request.headers.get("cf-ipcountry") != "T1":
|
||||||
|
@ -708,18 +726,20 @@ def api_is_repost():
|
||||||
@auth_required
|
@auth_required
|
||||||
def submit_post(v, sub=None):
|
def submit_post(v, sub=None):
|
||||||
|
|
||||||
title = request.values.get("title", "").strip()[:500].replace('','')
|
|
||||||
|
|
||||||
url = request.values.get("url", "").strip()
|
|
||||||
|
|
||||||
body = request.values.get("body", "").strip().replace('','')
|
|
||||||
|
|
||||||
def error(error):
|
def error(error):
|
||||||
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": error}, 403
|
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error": error}, 403
|
||||||
|
|
||||||
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
|
SUBS = [x[0] for x in g.db.query(Sub.name).order_by(Sub.name).all()]
|
||||||
return render_template("submit.html", SUBS=SUBS, v=v, error=error, title=title, url=url, body=body), 400
|
return render_template("submit.html", SUBS=SUBS, v=v, error=error, title=title, url=url, body=body), 400
|
||||||
|
|
||||||
|
title, err = guarded_value("title", 1, MAX_TITLE_LENGTH)
|
||||||
|
if err: return error(err[0]["error"])
|
||||||
|
|
||||||
|
url, err = guarded_value("url", 0, MAX_URL_LENGTH)
|
||||||
|
if err: return error(err[0]["error"])
|
||||||
|
|
||||||
|
body, err = guarded_value("body", 0, MAX_BODY_LENGTH)
|
||||||
|
if err: return error(err[0]["error"])
|
||||||
|
|
||||||
sub = request.values.get("sub")
|
sub = request.values.get("sub")
|
||||||
if sub: sub = sub.replace('/h/','').replace('s/','')
|
if sub: sub = sub.replace('/h/','').replace('s/','')
|
||||||
|
@ -817,15 +837,9 @@ def submit_post(v, sub=None):
|
||||||
embed = str(int(id))
|
embed = str(int(id))
|
||||||
|
|
||||||
|
|
||||||
if not url and not request.values.get("body") and not request.files.get("file") and not request.files.get("file2"):
|
if not url and not body and not request.files.get("file") and not request.files.get("file2"):
|
||||||
return error("Please enter a url or some text.")
|
return error("Please enter a url or some text.")
|
||||||
|
|
||||||
if not title:
|
|
||||||
return error("Please enter a better title.")
|
|
||||||
|
|
||||||
elif len(title) > 500:
|
|
||||||
return error("There's a 500 character limit for titles.")
|
|
||||||
|
|
||||||
dup = g.db.query(Submission).filter(
|
dup = g.db.query(Submission).filter(
|
||||||
Submission.author_id == v.id,
|
Submission.author_id == v.id,
|
||||||
Submission.deleted_utc == 0,
|
Submission.deleted_utc == 0,
|
||||||
|
@ -880,12 +894,6 @@ def submit_post(v, sub=None):
|
||||||
g.db.add(ma)
|
g.db.add(ma)
|
||||||
return redirect("/notifications")
|
return redirect("/notifications")
|
||||||
|
|
||||||
if len(str(body)) > 20000:
|
|
||||||
return error("There's a 20000 character limit for text body.")
|
|
||||||
|
|
||||||
if len(url) > 2048:
|
|
||||||
return error("There's a 2048 character limit for URLs.")
|
|
||||||
|
|
||||||
if v and v.admin_level > 2:
|
if v and v.admin_level > 2:
|
||||||
bet_options = []
|
bet_options = []
|
||||||
for i in bet_regex.finditer(body):
|
for i in bet_regex.finditer(body):
|
||||||
|
@ -941,6 +949,10 @@ def submit_post(v, sub=None):
|
||||||
|
|
||||||
is_bot = bool(request.headers.get("Authorization"))
|
is_bot = bool(request.headers.get("Authorization"))
|
||||||
|
|
||||||
|
# Invariant: these values are guarded and obey the length bound
|
||||||
|
assert len(title) <= MAX_TITLE_LENGTH
|
||||||
|
assert len(body) <= MAX_BODY_LENGTH
|
||||||
|
|
||||||
post = Submission(
|
post = Submission(
|
||||||
private=bool(request.values.get("private","")),
|
private=bool(request.values.get("private","")),
|
||||||
club=club,
|
club=club,
|
||||||
|
@ -949,10 +961,10 @@ def submit_post(v, sub=None):
|
||||||
app_id=v.client.application.id if v.client else None,
|
app_id=v.client.application.id if v.client else None,
|
||||||
is_bot = is_bot,
|
is_bot = is_bot,
|
||||||
url=url,
|
url=url,
|
||||||
body=body[:20000],
|
body=body,
|
||||||
body_html=body_html,
|
body_html=body_html,
|
||||||
embed_url=embed,
|
embed_url=embed,
|
||||||
title=title[:500],
|
title=title,
|
||||||
title_html=title_html,
|
title_html=title_html,
|
||||||
sub=sub,
|
sub=sub,
|
||||||
ghost=False,
|
ghost=False,
|
||||||
|
|
|
@ -291,7 +291,7 @@
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
<input type="hidden" name="formkey" value="{{v.formkey}}">
|
||||||
<input type="hidden" name="current_page" value="{{request.path}}">
|
<input type="hidden" name="current_page" value="{{request.path}}">
|
||||||
<textarea autocomplete="off" max-length="500" name="title" class="comment-box form-control rounded" required placeholder="title">{{p.title}}</textarea>
|
<input type="text" autocomplete="off" maxlength="500" name="title" class="comment-box form-control rounded" required placeholder="title" value="{{p.title}}"\>
|
||||||
<textarea autocomplete="off" name="body" {% if v.longpost %}minlength="280"{% endif %} maxlength="{% if v.bird %}140{% else %}20000{% endif %}" oninput="markdown('post-edit-box-{{p.id}}', 'post-edit-{{p.id}}');charLimit('post-edit-box-{{p.id}}','charcount-post-edit')" id="post-edit-box-{{p.id}}" form="post-edit-form-{{p.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add text to your post..." rows="10" data-id="{{p.id}}">{{p.body}}</textarea>
|
<textarea autocomplete="off" name="body" {% if v.longpost %}minlength="280"{% endif %} maxlength="{% if v.bird %}140{% else %}20000{% endif %}" oninput="markdown('post-edit-box-{{p.id}}', 'post-edit-{{p.id}}');charLimit('post-edit-box-{{p.id}}','charcount-post-edit')" id="post-edit-box-{{p.id}}" form="post-edit-form-{{p.id}}" class="comment-box form-control rounded" aria-label="With textarea" placeholder="Add text to your post..." rows="10" data-id="{{p.id}}">{{p.body}}</textarea>
|
||||||
|
|
||||||
<div class="text-small font-weight-bold mt-1" id="charcount-post-edit" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
|
<div class="text-small font-weight-bold mt-1" id="charcount-post-edit" style="right: 1rem; bottom: 0.5rem; z-index: 3;"></div>
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
<div id="urlblock">
|
<div id="urlblock">
|
||||||
<label for="URL" class="mt-3">URL</label>
|
<label for="URL" class="mt-3">URL</label>
|
||||||
<input autocomplete="off" class="form-control" id="post-url" aria-describedby="URLHelp" name="url" type="url" placeholder="Optional if you have text." value="{{request.values.get('url','')}}" required oninput="checkForRequired();hide_image();savetext();checkRepost(this);autoSuggestTitle()">
|
<input autocomplete="off" class="form-control" id="post-url" aria-describedby="URLHelp" name="url" type="url" placeholder="Optional if you have text." value="{{request.values.get('url','')}}" maxlength="2048" required oninput="checkForRequired();hide_image();savetext();checkRepost(this);autoSuggestTitle()">
|
||||||
<small id="system" class="form-text text-muted">To post an image, use a direct image link such as i.imgur.com</small>
|
<small id="system" class="form-text text-muted">To post an image, use a direct image link such as i.imgur.com</small>
|
||||||
</div>
|
</div>
|
||||||
<div id="image-upload-block">
|
<div id="image-upload-block">
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue