Daniel Sheffield 4 месяцев назад
Родитель
Сommit
dcd4ed3666

+ 3 - 5
rest/pyapi.py

@@ -1,5 +1,5 @@
 #
-# Copyright (c) Daniel Sheffield 2023
+# Copyright (c) Daniel Sheffield 2024
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
@@ -8,16 +8,14 @@ from bottle import (
     route, request, response,
     redirect, abort,
     template, static_file,
-    HTTPResponse, HTTPError,
 )
 from linkpreview import link_preview
 
-from .validate import CLIP_SIZE_LIMIT, _validate, get_file_mimetype, get_file_size, get_filename, validate, validate_file, validate_parameter, validate_url
+from .validate import CLIP_SIZE_LIMIT, get_file_mimetype, get_file_size, get_filename, validate_file
 from .hash_util import normalize_base32, blake, bytes_to_base32
-from .save import save, save_upload
+from .save import save_upload
 from .qr import get_qr_code
 from json import dumps, load
-import bottle
 
 SCHEME = "https://"
 HOST = ""

+ 2 - 20
rest/save.py

@@ -1,5 +1,5 @@
 #
-# Copyright (c) Daniel Sheffield 2023
+# Copyright (c) Daniel Sheffield 2024
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
@@ -9,7 +9,7 @@ import os
 import shutil
 from uuid import uuid4
 
-from .hash_util import DIGEST_SIZE_BYTES, blake, blake_file, bytes_to_base32
+from .hash_util import blake_file, bytes_to_base32
 from .qr import get_qr_code
 
 def save_qr(qr: bytes, _b32:str , directory: str) -> str:
@@ -18,24 +18,6 @@ def save_qr(qr: bytes, _b32:str , directory: str) -> str:
         f.write(qr)
 
 
-def save(content: bytes, tool: str, location: str, root='rest/static/files') -> str:
-    _bytes = blake(content, person=tool.encode('utf-8'))
-    _b32 = bytes_to_base32(_bytes)
-    directory = f'{root}/{_b32}'
-    try:
-        os.mkdir(directory, mode=0o700, dir_fd=None)
-    except FileExistsError:
-        pass
-    fd = os.open(f'{directory}/{_b32}.file', os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0o600)
-    with open(fd, "wb") as f:
-        f.write(content)
-    
-    link = f'{location}/{tool}/{_b32}'
-    svg = get_qr_code(content, fallback=link)
-    save_qr(svg, _b32, directory)
-    return _b32
-
-
 def save_filename(name: str, _b32: str, directory: str):
     fd = os.open(f'{directory}/{_b32}.name', os.O_WRONLY | os.O_TRUNC | os.O_CREAT, 0o600)
     with open(fd, "w") as f:

+ 0 - 8
rest/templates/buttongroup-clip.tpl

@@ -1,8 +0,0 @@
-% from rest.tool_color import color
-% include('button-style')
-<div class="pure-button-group" role="action" style="padding: 1em 0 0;">
-  <button class="button-resize pure-button" type="submit" form="paste" {{!disabled}} style="background-color: {{ color['clip'] }}"> Paste </button>
-  <button class="button-resize pure-button" type="submit" form="copy"> Edit as New </button>
-  % include('button-common')
-  <button class="button-resize pure-button" type="submit" form="download" {{!download_disabled}}> Download </button>
-</div>

+ 0 - 5
rest/templates/buttongroup-goto.tpl

@@ -1,5 +0,0 @@
-% from rest.tool_color import color
-<div class="pure-button-group" role="action" style="padding: 1em 0 0;">
-  <button class="button-resize pure-button" type="submit" form="goto" {{!disabled}} style="background-color: {{ color['goto'] }}"> Shrtn It! </button>
-  % include('button-common')
-</div>

+ 0 - 22
rest/templates/card-goto.tpl

@@ -1,22 +0,0 @@
-<style>
-.card img {
-    color: floralwhite;
-    object-fit: cover;
-    width: 100%;
-    height: 100%;
-}
-</style>
-<a href="{{link}}">
-<article class="card">
-  <header>
-    <h2>{{title}}</h2>
-  </header>
-  <figure> 
-    <img src="{{img}}" alt="{{title}}">
-    <figcaption>{{domain}}</figcaption>
-  </figure>
-  <div class="content">
-    {{link}}
-  </div>       
-</article>
-</a>

+ 0 - 38
rest/templates/form-clip.tpl

@@ -1,38 +0,0 @@
-% content = setdefault("content", "") or ""
-% disabled = (setdefault("disabled", False) and 'readonly="true"') or ""
-<form id="paste" method="{{ method }}" action="{{ action }}" enctype="multipart/form-data">
-  <style>
-textarea::-webkit-scrollbar {
-  width: 11px;
-}
-textarea {
-  color: #cccccc;
-  background-color: #080808;
-  scrollbar-width: thin;
-  scrollbar-color: var(--thumbBG) var(--scrollbarBG);
-}
-textarea::-webkit-scrollbar-track {
-  background: var(--scrollbarBG);
-}
-textarea::-webkit-scrollbar-thumb {
-  background-color: var(--thumbBG) ;
-  border-radius: 6px;
-  border: 3px solid var(--scrollbarBG);
-}
-  </style>
-  <textarea
-    style="width: 80%"
-    id="paste-text-area"
-    name="paste"
-    rows="30"
-    {{!disabled}}
-    required="true"
-    autofocus="true"
-    placeholder="Paste something here..."
-  >{{ content }}</textarea>
-</form>
-<form id="copy" method="post" action="{{ action }}">
-  <input id="copy-paste" name="paste" type="text" value="{{ content }}" hidden="true" />
-  <input id="copy" name="copy" type="text" value="true" hidden="true" />
-</form>
-% include('form-common')

+ 0 - 34
rest/templates/form-goto.tpl

@@ -1,34 +0,0 @@
-% content = setdefault("content", "") or ""
-% disabled = (setdefault("disabled", False) and 'readonly="true"') or ""
-<form id="goto" method="{{ method }}" action="{{ action }}">
-  <style>
-input[type="url"]::-webkit-scrollbar {
-  width: 11px;
-}
-input[type="url"] {
-  color: #cccccc;
-  background-color: #080808;
-  scrollbar-width: thin;
-  scrollbar-color: var(--thumbBG) var(--scrollbarBG);
-}
-input[type="url"]::-webkit-scrollbar-track {
-  background: var(--scrollbarBG);
-}
-input[type="url"]::-webkit-scrollbar-thumb {
-  background-color: var(--thumbBG) ;
-  border-radius: 6px;
-  border: 3px solid var(--scrollbarBG);
-}
-  </style>
-  <input type="url"
-    style="width: 80%"
-    id="input-url"
-    name="url"
-    {{!disabled}}
-    required="true"
-    autofocus="true"
-    placeholder="Paste URL here..."
-    value="{{ content }}"
-  ></input>
-</form>
-% include('form-common')

+ 0 - 55
rest/templates/goto.tpl

@@ -1,55 +0,0 @@
-% from rest.tool_color import color
-% link = setdefault("link", "") or ""
-% disabled = setdefault("disabled", "") and 'disabled="true"'
-% preview = setdefault("preview", None)
-<!DOCTYPE html>
-<html>
-  <head>
-    <style>
-html {
-  --scrollbarBG: #333333;
-  --thumbBG: #080808;
-}
-body {
-  background-color: #080808;
-  color: #cccccc;
-}
-img {
-  background-color: floralwhite;
-  color: black;
-  max-height: min(100vh, calc(100vw * 9 / 16));
-  max-width: calc(100vw - 2em);
-}
-    </style>
-    <title>GoTo...</title>
-    <link rel="manifest" href="/static/goto/manifest.json"/>
-    <link rel="icon" href="/static/goto/48.png" />
-    <meta name="viewport" content="width=device-width, initial-scale=1"/>
-    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@2.1.0/build/pure-min.css" integrity="sha384-yHIFVG6ClnONEA5yB5DJXfW2/KC173DIQrYoZMEtBvGzmf0PKiGyNEqe9N6BNDBH" crossorigin="anonymous"/>
-    <link rel="stylesheet" href="https://shandan.one/css/grids-responsive-min.css"/>
-    <link rel="stylesheet" href="https://shandan.one/css/responsive-visibility-collapse.css"/>
-  </head>
-  <body align="center" style="text-align: center">
-    <div class="pure-g">
-      <div class="pure-u-1">
-        % include('buttongroup-goto')
-      </div>
-      <div class="pure-u-1">
-        <div class="pure-button" style="margin: 1em 0 0; background: {{ color['goto'] }}">
-          <a href="{{!link}}" style="color: floralwhite;">{{ link }}</a>
-        </div>
-      </div>
-      <div class="pure-u-1">
-        <p><details><summary> Show QR code ...</summary><img src="{{qr}}"/></details></p>
-{{!form}}
-      </div>
-      <div class="pure-u-1-3"></div>
-      <div class="pure-u-1-3">
-      % if preview:
-      %   include('card-goto', **preview)
-      % end
-      </div>
-      <div class="pure-u-1-3"></div>
-    </div>
-  </body>
-</html>

+ 0 - 52
rest/templates/paste.tpl

@@ -1,52 +0,0 @@
-% from rest.tool_color import color
-% link = setdefault("link", "") or ""
-% disabled = setdefault("disabled", "") and 'disabled="true"'
-% download = setdefault("download", "") or ""
-% download_disabled = "" if download else 'disabled="true"'
-<!DOCTYPE html>
-<html>
-  <head>
-    <style>
-html {
-  --scrollbarBG: #333333;
-  --thumbBG: #080808;
-}
-body {
-  background-color: #080808;
-  color: #cccccc;
-}
-img {
-  background-color: floralwhite;
-  color: black;
-  max-height: min(100vh, calc(100vw * 9 / 16));
-  max-width: calc(100vw - 2em);
-}
-    </style>
-    <title>Paste</title>
-    <link rel="manifest" href="/static/clip/manifest.json"/>
-    <link rel="icon" href="/static/clip/48.png" />
-    <meta name="viewport" content="width=device-width, initial-scale=1"/>
-    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/purecss@2.1.0/build/pure-min.css" integrity="sha384-yHIFVG6ClnONEA5yB5DJXfW2/KC173DIQrYoZMEtBvGzmf0PKiGyNEqe9N6BNDBH" crossorigin="anonymous"/>
-    <link rel="stylesheet" href="https://shandan.one/css/grids-responsive-min.css"/>
-    <link rel="stylesheet" href="https://shandan.one/css/responsive-visibility-collapse.css"/>
-  </head>
-  <body align="center" style="text-align: center">
-    <div class="pure-g">
-      <div class="pure-u-1">
-        % include('buttongroup-clip')
-      </div>
-      <div class="pure-u-1">
-        <div class="pure-button" style="margin: 1em 0 0; background: {{ color['clip'] }};">
-          <a href="{{!link}}" style="color: floralwhite;">{{ link }}</a>
-        </div>
-      </div>
-      <div class="pure-u-1">
-        <p><details><summary> Show QR code ...</summary><img src="{{qr}}"/></details></p>
-{{!form}}
-<form id="download" method="get" action="{{ download }}">
-<input name="raw" type="text" hidden="true" value="true" />
-</form>
-      </div>
-    </div>
-  </body>
-</html>

+ 5 - 33
rest/validate.py

@@ -1,5 +1,5 @@
 #
-# Copyright (c) Daniel Sheffield 2023
+# Copyright (c) Daniel Sheffield 2024
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
@@ -24,10 +24,9 @@ from io import BufferedReader
 import mimetypes
 from itertools import chain
 import os
-from bottle import static_file, response, HTTPError, abort, LocalRequest, HTTPResponse
+from bottle import static_file, HTTPError, abort, LocalRequest, HTTPResponse
 from urllib.parse import urlparse, quote, quote_plus
 from .hash_util import blake_file, bytes_to_base32, blake
-import bottle
 
 # according to rfc3696
 URL_MUST_ESCAPE = bytes([
@@ -44,34 +43,6 @@ URL_MUST_ESCAPE = bytes([
 URL_SAFE = bytes(( i for i in range(int('0xff',0)+1) if i not in map(int, URL_MUST_ESCAPE) ))
 
 CLIP_SIZE_LIMIT = 65535
-def _validate(filename: str, tool: str, root='rest/static/files') -> bytes:
-    ret = static_file('/'.join([filename,]*2) + '.file', root=root)
-    if isinstance(ret, HTTPError):
-        return (
-            'abort', 404, f"No such `{tool.title()}`: {filename}"
-        )
-    if ret.status_code == 304:
-        return ret
-
-    if ret.content_length > CLIP_SIZE_LIMIT:
-        return (
-            'abort', 418, f"{tool.title()} size exceeds {CLIP_SIZE_LIMIT}"
-        )
-
-    content: bytes = ret.body.read() if isinstance(ret.body, BufferedReader) else ret.body.encode('utf-8')
-
-    _bytes = blake(content, person=tool.encode('utf-8'))
-    _b32 = bytes_to_base32(_bytes)
-    if _b32 != filename:
-        return (
-            'abort', 410, f"{tool.title()} content differs"
-        )
-    return content
-
-def validate(filename: str, tool: str, root='rest/static/files') -> bytes:
-    ret = _validate(filename, tool, root)
-    return getattr(bottle, ret[0])(*ret[1:]) if isinstance(ret, tuple) else ret
-
 
 def get_filename(filename: str, root: str = 'rest/static/files'):
     path = '/'.join([filename,]*2)
@@ -118,7 +89,7 @@ def validate_file(filename: str, root: str = 'rest/static/files', download=True,
 def validate_parameter(request: LocalRequest, name: str) -> bytes:
     if name not in request.params:
         return abort(400, f"Missing parameter: '{name}'")
-    
+
     # TODO: what is correct overhead for form content?
     OVERHEAD = 1024
     content: bytes = request.query.get(name, None)
@@ -134,11 +105,12 @@ def validate_parameter(request: LocalRequest, name: str) -> bytes:
         content: bytes = (content or request.params[name].encode('utf-8'))
     else:
         content: bytes = (content or request.params[name].encode('latin-1'))
-    
+
     if len(content) > CLIP_SIZE_LIMIT:
         return abort(418, f"Paste can not exceed {CLIP_SIZE_LIMIT} bytes")
     return content
 
+
 def validate_url(url: str) -> str:
     scheme, netloc, path, params, query, fragment = urlparse(url)