diff --git a/paste/models.py b/paste/models.py index 1ea6446..5e0c5df 100644 --- a/paste/models.py +++ b/paste/models.py @@ -3,6 +3,7 @@ from datetime import datetime, timedelta import shortuuid from django.core.validators import MaxLengthValidator from django.db import models +from django.urls import reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -25,15 +26,35 @@ class Paste(models.Model): """Computes size.""" self.size = len(self.content) + def get_absolute_url(self): + return reverse("short_paste", kwargs={"slug": self.slug}) + def incr_viewcount(self): """Increment view counter.""" self.viewcount = self.viewcount + 1 self.save() def __str__(self): - excerpt = repr(self.content[:100]) + ("..." if len(self.content) > 100 else "") + excerpt = repr(self.content.split("\n")[0][:100]) + ( + "..." if len(self.content) > 100 else "" + ) return f"{self.slug} - {excerpt}" + @classmethod + def choose_prefix(cls, filenames): + """Find a prefix free for all the given filenames. + + Such as /filename is unused. + """ + while True: + uuid = shortuuid.uuid() + for i in range(4, len(uuid) + 1): + potential_uuid = uuid[:i] + for filename in filenames: + slug = f"{potential_uuid}/{filename}" + if not any(cls.objects.filter(slug=slug) for filename in filenames): + return potential_uuid + def choose_slug(self): while True: uuid = shortuuid.uuid() diff --git a/paste/templates/base.html b/paste/templates/base.html index 9694da1..d0c27f1 100644 --- a/paste/templates/base.html +++ b/paste/templates/base.html @@ -1,11 +1,12 @@ -{% load i18n %}{% load compress %} +{% load i18n compress static %} + {% compress css %} - - + + {% endcompress %} {% block extrastyle %}{% endblock %} {% block title %}{{ DISPLAY_NAME }}{% endblock %} diff --git a/paste/templates/paste/show-markdown.html b/paste/templates/paste/show-markdown.html index fcb1953..91426a9 100644 --- a/paste/templates/paste/show-markdown.html +++ b/paste/templates/paste/show-markdown.html @@ -1,6 +1,7 @@ {% extends "base.html" %} +{% load static %} {% block extrastyle %} - + {% endblock %} {% block title %}{{ filename }}{% endblock %} {% block content %} diff --git a/paste/templates/paste/show-pygments.html b/paste/templates/paste/show-pygments.html index 7c77675..76bdd8c 100644 --- a/paste/templates/paste/show-pygments.html +++ b/paste/templates/paste/show-pygments.html @@ -1,6 +1,7 @@ {% extends "base.html" %} +{% load static %} {% block extrastyle %} - + {% endblock %} {% block title %}{{ filename }}{% endblock %} {% block content %} diff --git a/paste/urls.py b/paste/urls.py index 6983e94..3afeb2f 100644 --- a/paste/urls.py +++ b/paste/urls.py @@ -6,6 +6,6 @@ from webtools import settings urlpatterns = [ path("", views.index, name="index"), - path("static/", serve, {"document_root": settings.STATIC_ROOT}), - path("", views.show, name="short_paste"), + path("::/static/", serve, {"document_root": settings.STATIC_ROOT}), + path("", views.show, name="short_paste"), ] diff --git a/paste/utils.py b/paste/utils.py index f196400..5eefa17 100644 --- a/paste/utils.py +++ b/paste/utils.py @@ -10,7 +10,6 @@ from markdown.extensions.codehilite import CodeHiliteExtension from pygments.formatters import HtmlFormatter from pygments.lexers import get_lexer_by_name, get_lexer_for_filename - ALLOWED_TAGS = [ # Bleach Defaults "a", diff --git a/paste/views.py b/paste/views.py index 10d6108..954c140 100644 --- a/paste/views.py +++ b/paste/views.py @@ -1,10 +1,9 @@ from functools import lru_cache from mimetypes import common_types, types_map -from django.http import HttpResponse, HttpResponseRedirect +from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import get_object_or_404, render from django.template import RequestContext, loader -from django.urls import reverse from django.views.decorators.csrf import csrf_exempt from tabulate import tabulate @@ -14,7 +13,8 @@ from webtools import settings NON_STANDARD_TYPES_INV = {value: key for key, value in common_types.items()} STANDARD_TYPES_INV = {value: key for key, value in types_map.items()} -TYPES_INV = NON_STANDARD_TYPES_INV | STANDARD_TYPES_INV +HARDCODED_TYPES_INV = {"text/plain": ".txt"} +TYPES_INV = NON_STANDARD_TYPES_INV | STANDARD_TYPES_INV | HARDCODED_TYPES_INV def get_files(request): @@ -24,7 +24,9 @@ def get_files(request): return request.FILES content_type = request.headers.get("content-type", "") ext = TYPES_INV.get(content_type, "") - return {"request" + ext: request} + if ext: + return {"request" + ext: request} + return {"": request} @csrf_exempt @@ -35,36 +37,37 @@ def index(request): if request.headers.get("Expect") == "100-continue": return HttpResponse("") pastes = [] - for filename, the_file in get_files(request).items(): + files = get_files(request) + prefix = Paste.choose_prefix(list(files.keys())) + for filename, the_file in files.items(): + filename = filename.replace("\\", "/") paste = Paste( - filename=filename.split("/")[-1].split("\\")[-1], + slug=f"{prefix}/{filename}".rstrip("/"), + filename=filename.split("/")[-1], content=the_file.read().decode("UTF-8"), ) - paste.choose_slug() paste.compute_size() paste.save() - pastes.append( - ( - request.build_absolute_uri( - reverse("short_paste", kwargs={"slug": paste.slug}) - ), - paste.size, - paste.filename, - ) + pastes.append(paste) + + table = [ + ( + request.build_absolute_uri(paste.get_absolute_url()), + paste.size, + paste.filename, ) - - if not pastes: - return HttpResponse("error: Please provide a file.") - + for paste in pastes + ] return HttpResponse( - tabulate(pastes, headers=("URL", "size", "filename"), tablefmt="github") + "\n", + tabulate(table, headers=("URL", "size", "filename"), tablefmt="github") + "\n", content_type="text/plain", ) def show(request, slug): """Display paste.""" - + if slug.startswith("::"): + raise Http404() paste = get_object_or_404(Paste, slug=slug) paste.incr_viewcount() diff --git a/webtools/settings.py b/webtools/settings.py index a032e2e..f43eeec 100644 --- a/webtools/settings.py +++ b/webtools/settings.py @@ -40,7 +40,7 @@ MEDIA_URL = "" COMPRESS_ROOT = os.path.join(SITE_ROOT, "static") MEDIA_ROOT = os.path.join(SITE_ROOT, "assets") STATIC_ROOT = os.path.join(SITE_ROOT, "static") -STATIC_URL = "/static/" +STATIC_URL = "/::/static/" STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder", diff --git a/webtools/urls.py b/webtools/urls.py index 6c599f5..180af42 100644 --- a/webtools/urls.py +++ b/webtools/urls.py @@ -2,6 +2,6 @@ from django.contrib import admin from django.urls import path, include urlpatterns = [ - path('admin/', admin.site.urls), - path('', include('paste.urls')), + path("::/admin/", admin.site.urls), + path("", include("paste.urls")), ]