Ensure trailing slashes are handled correctly
This commit is contained in:
parent
97512598f5
commit
5e0cb48906
8
main.py
8
main.py
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue