|
@@ -11,7 +11,7 @@ from bottle import (
|
|
|
route, request, response,
|
|
|
redirect, abort,
|
|
|
template, static_file,
|
|
|
- FormsDict, HTTPError,
|
|
|
+ FormsDict, HTTPError, LocalRequest,
|
|
|
)
|
|
|
from psycopg import Cursor, connect
|
|
|
from psycopg.rows import TupleRow
|
|
@@ -96,6 +96,57 @@ DOMAIN = "shandan.one"
|
|
|
PORT = ""
|
|
|
LOCATION = SCHEME + (f"{HOST}." if HOST else "") + DOMAIN + (f":{PORT}" if PORT else "")
|
|
|
|
|
|
+def validate(filename: str) -> bytes:
|
|
|
+ ret = static_file('/'.join([filename,]*2) + '.file', root='app/rest/static')
|
|
|
+ if isinstance(ret, HTTPError):
|
|
|
+ return abort(404, f"No such paste: {filename}")
|
|
|
+
|
|
|
+ if ret.content_length > CLIP_SIZE_LIMIT:
|
|
|
+ return abort(418, f"Paste 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='clip'.encode('utf-8'))
|
|
|
+ _b32 = bytes_to_base32(_bytes)
|
|
|
+ if _b32 != filename:
|
|
|
+ return abort(410, f"Paste content differs")
|
|
|
+ return content
|
|
|
+
|
|
|
+
|
|
|
+def validate_parameter(request: LocalRequest, name: str) -> bytes:
|
|
|
+ if name not in request.params:
|
|
|
+ return abort(400, f"Missing parameter: '{name}'")
|
|
|
+
|
|
|
+
|
|
|
+ OVERHEAD = 1024
|
|
|
+ content: bytes = request.query.get(name, None)
|
|
|
+ content_length = request.content_length
|
|
|
+ if content_length == -1:
|
|
|
+ return abort(418, f"Content-Length must be specified")
|
|
|
+ if content_length > CLIP_SIZE_LIMIT + OVERHEAD:
|
|
|
+ return abort(418, f"Content-Length can not exceed {CLIP_SIZE_LIMIT+OVERHEAD}")
|
|
|
+
|
|
|
+
|
|
|
+ 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}")
|
|
|
+ return content
|
|
|
+
|
|
|
+
|
|
|
+def save(content: bytes, root='app/rest/static') -> str:
|
|
|
+ _bytes = blake(content, person='clip'.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)
|
|
|
+ return _b32
|
|
|
+
|
|
|
+
|
|
|
@route('/clip', method=['GET', 'POST'])
|
|
|
@route('/clip/', method=['GET', 'POST'])
|
|
|
def clip():
|
|
@@ -124,69 +175,28 @@ def clip():
|
|
|
)
|
|
|
|
|
|
if request.method == 'POST':
|
|
|
- if 'paste' not in request.params:
|
|
|
- return abort(400, "Missing parameter: 'paste'")
|
|
|
-
|
|
|
-
|
|
|
- OVERHEAD = 1024
|
|
|
- content = request.query.get("paste", None)
|
|
|
- content_length = request.content_length
|
|
|
- if content_length == -1:
|
|
|
- return abort(418, f"Content-Length must be specified")
|
|
|
- if content_length > CLIP_SIZE_LIMIT + OVERHEAD:
|
|
|
- return abort(418, f"Content-Length can not exceed {CLIP_SIZE_LIMIT+OVERHEAD}")
|
|
|
-
|
|
|
- content: bytes = content or request.params["paste"].encode('latin-1')
|
|
|
- if len(content) > CLIP_SIZE_LIMIT:
|
|
|
- return abort(418, f"Paste can not exceed {CLIP_SIZE_LIMIT}")
|
|
|
-
|
|
|
- if request.params.copy == 'true':
|
|
|
- response.content_type = 'text/html; charset=utf-8'
|
|
|
- form = template(
|
|
|
- 'clip-form',
|
|
|
- action='/clip',
|
|
|
- method='post',
|
|
|
- content=content,
|
|
|
- disabled=False
|
|
|
- )
|
|
|
- return template(
|
|
|
- 'paste',
|
|
|
- form=form,
|
|
|
- link=f'{LOCATION}/clip',
|
|
|
- disabled=False,
|
|
|
- download=None
|
|
|
- )
|
|
|
-
|
|
|
- _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_TRUNC | 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 redirect(f'/clip?hash={_b32}')
|
|
|
+ content = validate_parameter(request, 'paste')
|
|
|
|
|
|
-def validate(filename: str) -> bytes:
|
|
|
- ret = static_file('/'.join([filename,]*2) + '.file', root='app/rest/static')
|
|
|
- if isinstance(ret, HTTPError):
|
|
|
- return abort(404, f"No such paste: {filename}")
|
|
|
-
|
|
|
- if ret.content_length > CLIP_SIZE_LIMIT:
|
|
|
- return abort(418, f"Paste size exceeds {CLIP_SIZE_LIMIT}")
|
|
|
-
|
|
|
- content: bytes = ret.body.read() if isinstance(ret.body, BufferedReader) else ret.body.encode('utf-8')
|
|
|
+ if request.params.copy != 'true':
|
|
|
+ _b32 = save(content)
|
|
|
+ return redirect(f'/clip?hash={_b32}')
|
|
|
+
|
|
|
+ response.content_type = 'text/html; charset=utf-8'
|
|
|
+ form = template(
|
|
|
+ 'clip-form',
|
|
|
+ action='/clip',
|
|
|
+ method='post',
|
|
|
+ content=content,
|
|
|
+ disabled=False
|
|
|
+ )
|
|
|
+ return template(
|
|
|
+ 'paste',
|
|
|
+ form=form,
|
|
|
+ link=f'{LOCATION}/clip',
|
|
|
+ disabled=False,
|
|
|
+ download=None
|
|
|
+ )
|
|
|
|
|
|
- _bytes = blake(content, person='clip'.encode('utf-8'))
|
|
|
- _b32 = bytes_to_base32(_bytes)
|
|
|
- if _b32 != filename:
|
|
|
- return abort(410, f"Paste content differs")
|
|
|
- return content
|
|
|
|
|
|
@route('/clip/<filename:path>', method='GET')
|
|
|
def get_clip(filename):
|
|
@@ -200,3 +210,42 @@ def get_clip(filename):
|
|
|
return ret
|
|
|
|
|
|
return static_file('/'.join([filename,]*2) + '.file', root='app/rest/static')
|
|
|
+
|
|
|
+
|
|
|
+@route('/goto', method=['GET', 'POST'])
|
|
|
+@route('/goto/', method=['GET', 'POST'])
|
|
|
+def goto():
|
|
|
+ if request.method == 'GET':
|
|
|
+ _hash = request.params.hash
|
|
|
+ if _hash:
|
|
|
+ _hash = normalize_base32(_hash)
|
|
|
+ content = validate(_hash).decode('utf-8')
|
|
|
+ else:
|
|
|
+ content = None
|
|
|
+
|
|
|
+ if content and request.params.go == 'true':
|
|
|
+
|
|
|
+ return redirect(content)
|
|
|
+
|
|
|
+ link = f'{LOCATION}/goto/{_hash}' if content else f'{LOCATION}/goto'
|
|
|
+ disabled = True if content else False
|
|
|
+ response.content_type = 'text/html; charset=utf-8'
|
|
|
+ form = template(
|
|
|
+ 'goto-form',
|
|
|
+ action='/goto',
|
|
|
+ method='post',
|
|
|
+ content=content,
|
|
|
+ disabled=disabled
|
|
|
+ )
|
|
|
+ return template('goto', form=form, link=link, disabled=disabled)
|
|
|
+
|
|
|
+ if request.method == 'POST':
|
|
|
+ content = validate_parameter(request, 'url')
|
|
|
+ _b32 = save(content)
|
|
|
+ return redirect(f'/goto?hash={_b32}')
|
|
|
+
|
|
|
+
|
|
|
+@route('/goto/<filename:path>', method='GET')
|
|
|
+def redirect_goto(filename):
|
|
|
+ filename = normalize_base32(filename)
|
|
|
+ return redirect(f'/goto?hash={filename}&go=true')
|