Parcourir la source

post and get working

Daniel Sheffield il y a 1 an
Parent
commit
52187d7c14
2 fichiers modifiés avec 77 ajouts et 9 suppressions
  1. 17 7
      app/rest/hash_util.py
  2. 60 2
      app/rest/pyapi.py

+ 17 - 7
app/rest/hash_util.py

@@ -25,8 +25,13 @@ def md5hash(data: str):
 def shake(data: str):
     return shake_128(data.encode("utf-8"), usedforsecurity=False).hexdigest(DIGEST_SIZE_BYTES)
 
-def blake(data: str, person=None):
-    return blake2b(data.encode("utf-8"), usedforsecurity=False, digest_size=DIGEST_SIZE_BYTES, person=person).hexdigest()
+def blake(data: bytes, person: bytes = None) -> bytes:
+    return blake2b(
+        data,
+        usedforsecurity=False,
+        digest_size=DIGEST_SIZE_BYTES,
+        person=person
+    ).digest()
 
 def python(data: str):
     return hash(data)
@@ -87,9 +92,12 @@ def fix_padding(f):
         return f(fixed, *args, **kwargs)
     return wrap
 
+def normalize_base32(_b32: str):
+    return _b32.upper().zfill(DIGEST_SIZE_BYTES*8//5+1)
+
 def add_padding_base32(f):
     def wrap(*args, **kwargs):
-        return f(*args, **kwargs).zfill(DIGEST_SIZE_BYTES*8//5+1)
+        return normalize_base32(f(*args, **kwargs))
     return wrap
 
 @remove_padding
@@ -142,13 +150,15 @@ def base85_to_bytes(_b64: str) -> str:
 
 @add_padding_base32
 def hash_to_base32(_hash: int) -> str:
-    return b32.encode(_hash & DIGEST_SIZE_BITMASK).upper().zfill(DIGEST_SIZE_BYTES*8//5+1)
+    return b32.encode(_hash & DIGEST_SIZE_BITMASK)
 
+@add_padding_base32
 def hex_to_base32(_hex: str) -> str:
-    return b32.encode(hex_to_hash(_hex)).upper().zfill(DIGEST_SIZE_BYTES*8//5+1)
+    return b32.encode(hex_to_hash(_hex))
 
-def bytes_to_base32(_bytes: str) -> str:
-    return b32.encode(bytes_to_hash(_bytes)).upper().zfill(DIGEST_SIZE_BYTES*8//5+1)
+@add_padding_base32
+def bytes_to_base32(_bytes: bytes) -> str:
+    return b32.encode(bytes_to_hash(_bytes))
 
 #@fix_padding
 def base32_to_hash(_b64: str) -> str:

+ 60 - 2
app/rest/pyapi.py

@@ -3,14 +3,20 @@
 # All rights reserved
 #
 # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY
+from io import BufferedReader
 import os
 from threading import Thread
-from bottle import route, request, response, template, static_file, FormsDict
+from bottle import (
+    route, request, response,
+    redirect, abort, 
+    template, static_file,
+    FormsDict, HTTPError, HTTPResponse,
+)
 from psycopg import connect
 
 from app.data.filter import get_filter, get_query_param
 
-from .hash_util import hex_to_hash
+from .hash_util import blake, bytes_to_base32, hash_to_base32, hex_to_hash, normalize_base32
 from .route_decorators import normalize, normalize_query, poison, cursor
 from .query_to_xml import get_categories, get_groups, get_products, get_tags
 from .CachedLoadingPage import CachedLoadingPage
@@ -93,3 +99,55 @@ def products(cur):
 def tags(cur):
     response.content_type = 'application/xhtml+xml; charset=utf-8'
     return get_tags(cur, request.query)
+
+CLIP_SIZE_LIMIT = 65535
+
+@route('/clip', method=['GET', 'POST'])
+def clip():
+    if request.method == 'GET':
+        if request.query:
+            return redirect('/clip')
+        response.content_type = 'text/html; charset=utf-8'
+        form = template('clip-form', action='/clip', method='post', content=None)
+        return template('paste', form=form, link=None)
+    
+    if request.method == 'POST':
+        if 'paste' not in request.params:
+            return abort(400, "Missing arameter: 'paste'")
+        content = request.params['paste'].encode('utf-8')
+        if len(content) > CLIP_SIZE_LIMIT:
+            return abort(413, f"Paste size can not exceed {CLIP_SIZE_LIMIT}")
+        _bytes = blake(content, person='clip'.encode('utf-8'))
+        _b32 = bytes_to_base32(_bytes)
+        directory = f'app/rest/static/{_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_CREAT, 0o600)
+        with open(fd, "wb") as f:
+            f.write(content)
+
+        form = template('clip-form', action='/clip', method='post', content=content)
+        response.content_type = 'text/html; charset=utf-8'
+        return HTTPResponse(template('paste', form=form, link=f'/clip/{_b32}'), 201)
+
+@route('/clip/<filename:path>', method='GET')
+def get_clip(filename):
+    f = normalize_base32(filename)
+
+    ret = static_file('/'.join([f,]*2) + '.file', root='app/rest/static')
+    if isinstance(ret, HTTPError):
+        return ret
+
+    if ret.content_length > CLIP_SIZE_LIMIT:
+        return abort(418, f"Paste size exceeds {CLIP_SIZE_LIMIT}")
+
+    content: str = ret.body.read() if isinstance(ret.body, BufferedReader) else ret.body.encode('utf-8')
+
+    _bytes = blake(content, person='clip'.encode('utf-8'))
+    _b32 = bytes_to_base32(_bytes)
+    if _b32 != f:
+        return abort(410, f"Paste content differs")
+    
+    return HTTPResponse(content, 200)