Ensure trailing slashes are handled correctly

This commit is contained in:
Scott Wallace 2025-02-12 09:29:23 +00:00
parent 97512598f5
commit 5e0cb48906
Signed by: scott
SSH key fingerprint: SHA256:+LJug6Dj01Jdg86CILGng9r0lJseUrpI0xfRqdW9Uws
2 changed files with 30 additions and 36 deletions

View file

@ -1,6 +1,4 @@
""" """Main Flask-based app for Slinky."""
Main Flask-based app for Slinky
"""
from flask import Flask, Response, render_template from flask import Flask, Response, render_template
from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.proxy_fix import ProxyFix
@ -15,10 +13,10 @@ app.register_blueprint(slinky_webapp)
@app.route("/") @app.route("/")
@protect @protect
def index() -> Response: def index() -> Response:
""" """Index/Landing page.
Index/Landing page
Returns: Returns:
str: string of page content str: string of page content
""" """
return Response(render_template("index.html"), 200) return Response(render_template("index.html"), 200)

View file

@ -1,12 +1,11 @@
""" """Web component."""
Web component
"""
import logging import logging
import os import os
from datetime import datetime import pathlib
from datetime import UTC, datetime
from functools import wraps from functools import wraps
from typing import Any, Callable from typing import Callable
import yaml import yaml
from flask import Blueprint, Response, render_template, request from flask import Blueprint, Response, render_template, request
@ -18,22 +17,18 @@ from slinky import Slinky, random_string
slinky_webapp = Blueprint("webapp", __name__, template_folder="templates") slinky_webapp = Blueprint("webapp", __name__, template_folder="templates")
with open("config.yaml", encoding="utf-8-sig") as conffile: with pathlib.Path("config.yaml").open(encoding="utf-8-sig") as conffile:
cfg = yaml.safe_load(conffile) cfg = yaml.safe_load(conffile)
class DelForm(FlaskForm): class DelForm(FlaskForm):
""" """Delete form definition."""
Delete form definition
"""
delete = HiddenField("delete") delete = HiddenField("delete")
class AddForm(FlaskForm): class AddForm(FlaskForm):
""" """Add form definition."""
Add form definition
"""
shortcode = StringField( shortcode = StringField(
"Shortcode", "Shortcode",
@ -79,18 +74,18 @@ class AddForm(FlaskForm):
def protect(func: Callable[..., Response]) -> Callable[..., Response]: def protect(func: Callable[..., Response]) -> Callable[..., Response]:
""" """Protect the admin interface.
Decorator that will protect the admin interface
Args: Args:
func (Callable): Wrapped function func (Callable): Wrapped function
Returns: Returns:
Callable: Function wrapper Callable: Function wrapper
""" """
@wraps(func) @wraps(func)
def check_ip(*args: Any, **kwargs: Any) -> Response: def check_ip(*args: ..., **kwargs: ...) -> Response:
remote_addr = request.remote_addr remote_addr = request.remote_addr
if "x-forwarded-for" in request.headers: if "x-forwarded-for" in request.headers:
@ -99,10 +94,7 @@ def protect(func: Callable[..., Response]) -> Callable[..., Response]:
if "x-real-ip" in request.headers: if "x-real-ip" in request.headers:
remote_addr = request.headers["x-real-ip"] remote_addr = request.headers["x-real-ip"]
if ( if os.environ.get("FLASK_ENV", "") != "development" and remote_addr not in cfg["allowed_ips"]:
os.environ.get("FLASK_ENV", "") != "development"
and remote_addr not in cfg["allowed_ips"]
):
logging.warning("Protected URL access attempt from %s", remote_addr) logging.warning("Protected URL access attempt from %s", remote_addr)
return Response("Not found", 404) return Response("Not found", 404)
return func(*args, **kwargs) return func(*args, **kwargs)
@ -112,28 +104,32 @@ def protect(func: Callable[..., Response]) -> Callable[..., Response]:
@slinky_webapp.route("/<path:path>", strict_slashes=False) @slinky_webapp.route("/<path:path>", strict_slashes=False)
def try_path_as_shortcode(path: str) -> Response: def try_path_as_shortcode(path: str) -> Response:
""" """Try the initial path as a shortcode, redirect if found.
Try the initial path as a shortcode, redirect if found
Returns: Returns:
Response: redirect if found, otherwise 404 Response: redirect if found, otherwise 404
""" """
path = path.strip("/")
should_redirect = True should_redirect = True
slinky = Slinky(cfg["db"]) slinky = Slinky(cfg["db"])
shortcode = slinky.get_by_shortcode(path) shortcode = slinky.get_by_shortcode(path)
if shortcode.url: if shortcode.url:
if shortcode.fixed_views == 0: if shortcode.fixed_views == 0:
logging.warning("Shortcode out of views") logging.warning("Shortcode out of views")
should_redirect = False should_redirect = False
elif shortcode.fixed_views > 0: elif shortcode.fixed_views > 0:
slinky.remove_view(shortcode.id) slinky.remove_view(shortcode.id)
if datetime.fromisoformat(shortcode.expiry) < datetime.now(): if datetime.fromisoformat(shortcode.expiry).astimezone(UTC) < datetime.now(UTC):
logging.warning("Shortcode expired") logging.warning("Shortcode expired")
should_redirect = False should_redirect = False
if should_redirect: if should_redirect:
return Response( return Response(
"Redirecting...", status=302, headers={"location": shortcode.url} "Redirecting...",
status=302,
headers={"location": shortcode.url},
) )
return Response("Not found", 404) return Response("Not found", 404)
@ -142,11 +138,11 @@ def try_path_as_shortcode(path: str) -> Response:
@slinky_webapp.route("/_/add", methods=["GET", "POST"]) @slinky_webapp.route("/_/add", methods=["GET", "POST"])
@protect @protect
def add() -> Response: def add() -> Response:
""" """Create and add a new shorturl.
Create and add a new shorturl
Returns: Returns:
Response: HTTP response Response: HTTP response
""" """
slinky = Slinky(cfg["db"]) slinky = Slinky(cfg["db"])
@ -207,11 +203,11 @@ def add() -> Response:
@slinky_webapp.route("/_/list", methods=["GET", "POST"]) @slinky_webapp.route("/_/list", methods=["GET", "POST"])
@protect @protect
def lister() -> Response: def lister() -> Response:
""" """List the shortcodes, URLs, etc.
List the shortcodes, URLs, etc.
Returns: Returns:
Response: HTTP response Response: HTTP response
""" """
form = DelForm(meta={"csrf": False}) form = DelForm(meta={"csrf": False})
slinky = Slinky(cfg["db"]) slinky = Slinky(cfg["db"])
@ -227,17 +223,17 @@ def lister() -> Response:
@slinky_webapp.route("/_/edit/<int:id>", methods=["GET", "POST"]) @slinky_webapp.route("/_/edit/<int:id>", methods=["GET", "POST"])
@protect @protect
def edit(id: int) -> Response: # pylint: disable=invalid-name,redefined-builtin def edit(shortcut_id: int) -> Response: # pylint: disable=invalid-name,redefined-builtin
""" """Edit the shortcode.
Edit the shortcode.
Returns: Returns:
Response: HTTP response Response: HTTP response
""" """
form = DelForm(meta={"csrf": False}) form = DelForm(meta={"csrf": False})
slinky = Slinky(cfg["db"]) slinky = Slinky(cfg["db"])
logging.debug("Editing: %d", id) logging.debug("Editing: %d", shortcut_id)
if form.is_submitted(): if form.is_submitted():
slinky.delete_by_shortcode(form.delete.data.strip()) slinky.delete_by_shortcode(form.delete.data.strip())