# # Copyright (c) Daniel Sheffield 2023 # All rights reserved # # THIS SOFTWARE IS PROVIDED AS IS WITHOUT WARRANTY from io import BytesIO from bottle import ( route, request, response, redirect, abort, template, static_file, HTTPResponse, HTTPError, ) from linkpreview import link_preview from .validate import CLIP_SIZE_LIMIT, get_file_mimetype, get_file_size, get_filename, validate, validate_file, validate_parameter, validate_url from .hash_util import normalize_base32 from .save import save, save_upload SCHEME = "https://" HOST = "" DOMAIN = "shandan.one" PORT = "" LOCATION = SCHEME + (f"{HOST}." if HOST else "") + DOMAIN + (f":{PORT}" if PORT else "") @route('/clip', method=['GET', 'POST']) def clip(): if request.method == 'GET': _hash = request.params.hash if _hash: _hash = normalize_base32(_hash) content = validate(_hash).decode('utf-8') else: content = None link = f'{LOCATION}/clip/{_hash}' if content else f'{LOCATION}/clip' response.content_type = 'text/html; charset=utf-8' form = template( 'clip-form', action='/clip', method='post', content=content, disabled=True if content else False ) return template( 'paste', form=form, qr=f'{LOCATION}/clip/{_hash}.qr' if content else f'{LOCATION}/grocery/static/clip-qr.svg', link=link, disabled=True if content else False, download=f'/clip/{_hash}' if content else None ) if request.method == 'POST': content = validate_parameter(request, 'paste') if request.params.copy != 'true': _b32 = save(content, LOCATION) 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 ) link = f'{LOCATION}/clip' return template( 'paste', form=form, qr=f'{LOCATION}/grocery/static/clip-qr.svg', link=link, disabled=False, download=None ) @route('/clip/', method='GET') def get_clip(filename): ext = 'file' if filename and filename.endswith('.qr'): filename, ext = filename.split('.', 1) filename = filename and normalize_base32(filename) path = f'{filename}/{filename}.{ext}' if ext == 'qr': return static_file(path, root='rest/static', mimetype='image/svg+xml') filename = filename and normalize_base32(filename) if not request.params.raw.lower() == 'true': # TODO: return a form with timeout to a GET instead ? return redirect(f'/clip?hash={filename}') ret = validate(filename) if isinstance(ret, HTTPError): return ret return static_file(path, root='rest/static') @route('/upload', method=['GET', 'POST']) def upload(): if request.method == 'GET': _hash = request.params.hash mimetype = None if _hash: _hash = normalize_base32(_hash) size = get_file_size(_hash) name = get_filename(_hash) mimetype = get_file_mimetype(name) if mimetype is not True and mimetype.startswith('text'): mimetype = None if size > CLIP_SIZE_LIMIT else mimetype link = f'{LOCATION}/upload/{_hash}' if _hash else f'{LOCATION}/upload' response.content_type = 'text/html; charset=utf-8' disabled = True if _hash else False form = template('file-form', action='/upload', method='post', disabled=disabled) return template( 'upload', form=form, qr=f'{LOCATION}/upload/{_hash}.qr' if _hash else f'{LOCATION}/grocery/static/upload-qr.svg', link=link, mimetype=mimetype, disabled=disabled ) if request.method == 'POST': if 'paste' not in request.files: return abort(400, "Parameter 'paste' must be specified") upload = request.files['paste'] if isinstance(upload.file, BytesIO): if len(upload.file.read()) == 0: return abort(400, "File is empty") _b32 = save_upload(upload.raw_filename, LOCATION, upload.file, root='rest/static') return redirect(f'/upload?hash={_b32}') @route('/upload/', method='GET') def get_upload(filename): ext = 'file' if filename and filename.endswith('.qr'): filename, ext = filename.split('.', 1) filename = filename and normalize_base32(filename) path = f'{filename}/{filename}.{ext}' if ext == 'qr': return static_file(path, root='rest/static', mimetype='image/svg+xml') download = True mimetype = True if request.params.download == "false": download = False mimetype = request.params.mimetype or None return validate_file(filename, download=download, mimetype=mimetype) @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': target = validate_url(content) return redirect(target) 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 ) preview = dict() if content: try: page = link_preview(link, parser="lxml") preview['title'] = page.title preview['img'] = page.absolute_image preview['domain'] = page.site_name preview['link'] = content except: pass return template( 'goto', form=form, qr=f'{LOCATION}/goto/{_hash}.qr' if content else f'{LOCATION}/grocery/static/goto-qr.svg', link=link, disabled=disabled, preview=preview, ) if request.method == 'POST': content = validate_parameter(request, 'url') _b32 = save(content, LOCATION) # validate but save content unmodified _ = validate_url(content.decode('utf-8')) return redirect(f'/goto?hash={_b32}') @route('/goto/', method='GET') def redirect_goto(filename): ext = 'file' if filename and filename.endswith('.qr'): filename, ext = filename.split('.', 1) filename = filename and normalize_base32(filename) path = f'{filename}/{filename}.{ext}' if ext == 'qr': return static_file(path, root='rest/static', mimetype='image/svg+xml') return redirect(f'/goto?hash={filename}&go=true') @route('//', method='GET') def redirect_trailing_slash(any): return redirect(f'/{any}')