~fluix/tilde

e9717a705feef09d69ad0c3617a08f11b82cace8 — Steven Guikal 4 months ago 6607d01
Remove paste and shortener services

It's likely better to rely on already established alternatives.
8 files changed, 0 insertions(+), 260 deletions(-)

M README.md
D paste/__init__.py
D paste/templates/submit.html
D paste/templates/view.html
D paste/views.py
D shortener/__init__.py
D shortener/templates/shorten.html
D shortener/views.py
M README.md => README.md +0 -2
@@ 12,5 12,3 @@ A tilde site and service suite for friends to come together.

 - `auth`: Authentication management service.
 - `flatpages`: Static index, information, and help pages.
 - `paste`: [**WIP**] Paste service.
 - `shortener`: [**WIP**] Link shortener service.

D paste/__init__.py => paste/__init__.py +0 -0
D paste/templates/submit.html => paste/templates/submit.html +0 -50
@@ 1,50 0,0 @@
<!--
SPDX-FileCopyrightText: 2021 Steven Guikal <void@fluix.one>

SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: CC-BY-SA-4.0
-->

{% extends "base.html" %}

{% block head %}
{% endblock %}

{% block main %}
  <h1>Pastebin</h1>
  <p>A simple pastebin service which allows entering any kind of text content or uploading an existing file.</p>
  <form method="POST" enctype="multipart/form-data">
    <label for="length">Link Length</label>
    <select name="length" id="length" required>
      <option value="0">Short</option>
      <option value="32">Secure</option>
    </select>

    <label for="expiry">Expiry</label>
    <select name="expiry" id="expiry" required>
      <option value="0">Never</option>
      <option value="3600">1 hour</option>
      <option value="21600">6 hours</option>
      <option value="43200">12 hours</option>
      <option value="86400">1 day</option>
      <option value="172800">2 days</option>
      <option value="604800">7 days</option>
      <option value="2592000">30 days</option>
    </select>
    <small>An upper limit; pastes may expire sooner.</small>

    <label for="ext">File Extension</label>
    <input type="text" name="ext" id="ext" value="txt" pattern="[a-zA-Z]">
    <small>ASCII letters. Only needed with source.</small>

    <label for="source">Source</label>
    <textarea name="source" id="source"></textarea>
    <small>Enter <i>either</i> this source <i>or</i> upload a file below.</small>

    <label for="file">File</label>
    <input type="file" id="file" name="file">
    <small>Dragging and dropping or pasting works too.</small>

    <input type="submit" value="Upload">
  </form>
{% endblock %}

D paste/templates/view.html => paste/templates/view.html +0 -34
@@ 1,34 0,0 @@
<!--
SPDX-FileCopyrightText: 2021 Steven Guikal <void@fluix.one>

SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: CC-BY-SA-4.0
-->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Paste {{ code }}</title>
    <link rel="stylesheet" href="{{ url_for("static", filename="style.css") }}" type="text/css">
  </head>
  <body>
    {% if type == "text" %}
      <pre style="overflow: scroll">{{ source }}</pre>
    {% elif type == "audio" %}
      <audio src="{{ raw }}" controls>
        <a href="{{ raw }}">View Raw</a>
      </audio>
    {% elif type == "image" %}
    <a href="{{ raw }}"><img src="{{ raw }}" width="100%"></a>
    {% elif type == "video" %}
      <video src="{{ raw }}" controls>
        <a href="{{ raw }}">View Raw</a>
      </video>
    {% else %}
      <p>Can't display paste with <code>{{ type }}/*</code> mimetype.</p>
    {% endif %}
    <a href="{{ raw }}">View Raw</a>
  </body>
</html>

D paste/views.py => paste/views.py +0 -81
@@ 1,81 0,0 @@
# SPDX-FileCopyrightText: 2021 Steven Guikal <void@fluix.one>
#
# SPDX-License-Identifier: AGPL-3.0-only

import mimetypes
import os
import secrets
import string
from flask import (
    Blueprint,
    abort,
    current_app,
    redirect,
    render_template,
    request,
    send_from_directory,
    url_for,
)
from core import redis


paste = Blueprint("paste", __name__, template_folder="templates")


@paste.route("/", methods=["GET", "POST"])
def submit():
    if request.method != "POST":
        return render_template("submit.html")

    try:
        length = int(request.form.get("length") or 0) + 3
        expiry = int(request.form.get("expiry") or 0)
        ext = request.form.get("ext")
        for c in ext:
            if c not in string.ascii_letters:
                raise ValueError
    except ValueError:
        abort(400)

    code = secrets.token_urlsafe(length)
    if request.files:
        file = request.files["file"]
        ext = os.path.splitext(file.filename)[1][1:] or ext or "bin"
        path = f"{current_app.config['MEDIA_ROOT']}{code}.{ext}"
        file.save(path)
    else:
        ext = ext or "txt"
        path = f"{current_app.config['MEDIA_ROOT']}{code}.{ext}"
        with open(path, "w") as f:
            f.write(request.form.get("source"))
    redis.hset(f"paste:{code}", mapping={"ext": ext})

    if expiry:
        redis.expire(f"paste:{code}", expiry)
    return redirect(url_for("paste.view", code=code))


@paste.route("/<code>")
def view(code):
    if (ext := redis.hget(f"paste:{code}", "ext")) is None:
        abort(404)
    mtype, _ = mimetypes.guess_type(f"{code}.{ext.decode()}")
    source = None
    if mtype and mtype.startswith("text"):
        with open(f"{current_app.config['MEDIA_ROOT']}{code}.{ext.decode()}", "r") as f:
            source = f.read()
    raw = url_for("paste.raw", code=code, ext=ext.decode())
    return render_template(
        "view.html", code=code, source=source, raw=raw, type=mtype.split("/")[0]
    )


@paste.route("/<code>.<ext>")
def raw(code, ext):
    if not (ext := redis.hget(f"paste:{code}", "ext")):
        abort(404)
    resp = send_from_directory(
        current_app.config["MEDIA_ROOT"], f"{code}.{ext.decode()}"
    )
    resp.headers["Content-Security-Policy"] = "script-src none;"
    return resp

D shortener/__init__.py => shortener/__init__.py +0 -0
D shortener/templates/shorten.html => shortener/templates/shorten.html +0 -51
@@ 1,51 0,0 @@
<!--
SPDX-FileCopyrightText: 2021 Steven Guikal <void@fluix.one>

SPDX-License-Identifier: AGPL-3.0-only
SPDX-License-Identifier: CC-BY-SA-4.0
-->

{% extends "base.html" %}

{% block head %}
  <title>Link shortener</title>
{% endblock %}

{% block main %}
  <h1>Link Shortener</h1>
  {% if request.query_string %}
    <p class="toast green">Link shortened to: <a href="{{ url_for("shortener.lengthen", code=request.query_string.decode()) }}">{{ request.host }}{{ url_for("shortener.lengthen", code=request.query_string.decode()) }}</a></p>
    <p>Shorten another link.</p>
  {% else %}
    <p>A simple link shortener service. Please do not use this for links that need to exist for a significant amount of time to avoid link rot.</p>
  {% endif %}

  {% if error %}
    <p class="toast red">{{ error }}</p>
  {% endif %}

  <form method="POST">
    <label for="code">Code</label>
    <input type="text" name="code" id="code" {% if values and values.get("code") %}value="{{ values.get("code") }}"{% endif %}>
    <small>Make this relevant to the linked content. <b>Do not misrepresent it.</b></small>

    <label for="link">Link</label>
    <input type="url" name="link" id="link" required {% if values and values.get("link") %}value="{{ values.get("link") }}"{% endif %}>

    <label for="expiry">Expiry <small>(seconds, optional)</small></label>
    <input type="text" name="expiry" id="expiry" list="expiry-list" {% if values and values.get("expiry") %}value="{{ values.get("expiry") }}"{% endif %}>
    <datalist id="expiry-list">
      <option value="0">Never</option>
      <option value="3600">1 hour</option>
      <option value="21600">6 hours</option>
      <option value="43200">12 hours</option>
      <option value="86400">1 day</option>
      <option value="172800">2 days</option>
      <option value="604800">7 days</option>
      <option value="2592000">30 days</option>
    </datalist>
    <small>An upper limit; links may expire sooner.</small>

    <input type="submit" value="Shorten">
  </form>
{% endblock %}

D shortener/views.py => shortener/views.py +0 -42
@@ 1,42 0,0 @@
# SPDX-FileCopyrightText: 2021 Steven Guikal <void@fluix.one>
#
# SPDX-License-Identifier: AGPL-3.0-only

import secrets
from urllib.parse import urlsplit, urlunsplit, unquote
from flask import Blueprint, abort, redirect, render_template, request, url_for
from core import redis


shortener = Blueprint("shortener", __name__, template_folder="templates")


@shortener.route("/", methods=["GET", "POST"])
def shorten():
    if request.method != "POST":
        return render_template("shorten.html")

    code = request.form.get("code")
    key = f"shorten:{code}"
    if redis.exists(key):
        return render_template(
            "shorten.html", error="This code already exists!", values=request.form
        )
    try:
        link = urlsplit(request.form.get("link"))
        expiry = int(request.form.get("expiry") or 0)
    except ValueError:
        abort(400)
    redis.set(key, urlunsplit(link))
    if expiry:
        redis.expire(key, expiry)
    return redirect(f"?{code}")


@shortener.route("/<code>")
def lengthen(code):
    if link := redis.get(f"shorten:{unquote(code)}"):
        resp = redirect(link.decode(), 307)
        resp.headers["Referrer-Policy"] = "no-referrer"
        return resp
    abort(404)