Merge branch 'frost' into pr-eager-content

This commit is contained in:
Ben Rog-Wilhelm 2022-12-17 15:15:41 -08:00 committed by Ben Rog-Wilhelm
commit b4efd75753
59 changed files with 2193 additions and 581 deletions

View file

@ -39,20 +39,19 @@ 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):
def guarded_value(val, min_len, max_len) -> str:
'''
Get request value `val` and ensure it is within length constraints
Requires a request context and either aborts early or returns a good value
'''
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)
if len(raw) < min_len: abort(400, f"Minimum length for {val} is {min_len}")
if len(raw) > max_len: abort(400, f"Maximum length for {val} is {max_len}")
# TODO: it may make sense to do more sanitisation here
return raw
@app.post("/toggle_club/<pid>")
@auth_required
@ -131,7 +130,7 @@ def post_id(pid, anything=None, v=None, sub=None):
post = get_post(pid, v=v)
if post.over_18 and not (v and v.over_18) and session.get('over_18', 0) < int(time.time()):
if request.headers.get("Authorization") or request.headers.get("xhr"): return {"error":"Must be 18+ to view"}, 451
if request.headers.get("Authorization") or request.headers.get("xhr"): abort(403, "Must be 18+ to view")
return render_template("errors/nsfw.html", v=v)
if v: defaultsortingcomments = v.defaultsortingcomments
@ -408,11 +407,8 @@ def edit_post(pid, v):
if p.author_id != v.id and not (v.admin_level > 1 and v.admin_level > 2): abort(403)
title, err = guarded_value("title", 1, MAX_TITLE_LENGTH)
if err: return err
body, err = guarded_value("body", 0, MAX_BODY_LENGTH)
if err: return err
title = guarded_value("title", 1, MAX_TITLE_LENGTH)
body = guarded_value("body", 0, MAX_BODY_LENGTH)
if title != p.title:
p.title = title
@ -434,24 +430,24 @@ def edit_post(pid, v):
file.save("video.mp4")
with open("video.mp4", 'rb') as f:
try: req = requests.request("POST", "https://api.imgur.com/3/upload", headers={'Authorization': f'Client-ID {IMGUR_KEY}'}, files=[('video', f)], timeout=5).json()['data']
except requests.Timeout: return {"error": "Video upload timed out, please try again!"}
except requests.Timeout: abort(500, "Video upload timed out, please try again!")
try: url = req['link']
except:
error = req['error']
if error == 'File exceeds max duration': error += ' (60 seconds)'
return {"error": error}, 400
abort(400, error)
if url.endswith('.'): url += 'mp4'
if app.config['MULTIMEDIA_EMBEDDING_ENABLED']:
body += f"\n\n![]({url})"
else:
body += f'\n\n<a href="{url}">{url}</a>'
else: return {"error": "Image/Video files only"}, 400
else: abort(400, "Image/Video files only")
body_html = sanitize(body, edit=True)
p.body = body
if len(body_html) > 40000: return {"error":"Submission body_html too long! (max 40k characters)"}, 400
if len(body_html) > 40000: abort(400, "Submission body_html too long! (max 40k characters)")
p.body_html = body_html
@ -678,19 +674,14 @@ def api_is_repost():
def submit_post(v, sub=None):
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"): abort(400, error)
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
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"])
title = guarded_value("title", 1, MAX_TITLE_LENGTH)
url = guarded_value("url", 0, MAX_URL_LENGTH)
body = guarded_value("body", 0, MAX_BODY_LENGTH)
sub = request.values.get("sub")
if sub: sub = sub.replace('/h/','').replace('s/','')
@ -1092,35 +1083,42 @@ def unsave_post(pid, v):
@app.post("/pin/<post_id>")
@auth_required
def api_pin_post(post_id, v):
post = get_post(post_id)
if v.id != post.author_id: abort(403, "Only the post author's can do that!")
post.is_pinned = not post.is_pinned
g.db.add(post)
post = g.db.query(Submission).filter_by(id=post_id).one_or_none()
if post:
if v.id != post.author_id: return {"error": "Only the post author's can do that!"}
post.is_pinned = not post.is_pinned
g.db.add(post)
cache.delete_memoized(User.userpagelisting)
g.db.commit()
if post.is_pinned: return {"message": "Post pinned!"}
else: return {"message": "Post unpinned!"}
return {"error": "Post not found!"}
cache.delete_memoized(User.userpagelisting)
g.db.commit()
if post.is_pinned: return {"message": "Post pinned!"}
else: return {"message": "Post unpinned!"}
@app.get("/submit/title")
@limiter.limit("6/minute")
@auth_required
def get_post_title(v):
POST_TITLE_TIMEOUT = 5
url = request.values.get("url")
if not url: abort(400)
if not url or '\\' in url: abort(400)
url = url.strip()
if not url.startswith('http'): abort(400)
checking_url = url.lower().split('?')[0].split('%3F')[0]
if any((checking_url.endswith(f'.{x}') for x in NO_TITLE_EXTENSIONS)):
abort(400)
try: x = requests.get(url, headers=titleheaders, timeout=5, proxies=proxies)
try:
x = gevent.with_timeout(POST_TITLE_TIMEOUT, requests.get,
url, headers=titleheaders, timeout=POST_TITLE_TIMEOUT,
proxies=proxies)
except: abort(400)
content_type = x.headers.get("Content-Type")
if not content_type or "text/html" not in content_type: abort(400)
soup = BeautifulSoup(x.content, 'lxml')
match = html_title_regex.search(x.text)
if match and match.lastindex >= 1:
title = html.unescape(match.group(1))
else: abort(400)
title = soup.find('title')
if not title: abort(400)
return {"url": url, "title": title.string}
return {"url": url, "title": title}