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)