diff --git a/app.py b/app.py index c7888e4..2e55c93 100644 --- a/app.py +++ b/app.py @@ -261,10 +261,11 @@ def create_paste(): else: return jsonify({'error': 'Provide either encrypted_data or content'}), 400 - allowed_expiry = set(_pastes.get('allow_expiry_options', ['never'])) - expires_in = data.get('expires_in', 'never') + allowed_expiry = set(_pastes.get('allow_expiry_options', ['1year'])) + expires_in = data.get('expires_in', _pastes.get('default_expiry', '1year')) if expires_in not in allowed_expiry: - expires_in = 'never' + # Fallback to the first allowed option if everything is missing + expires_in = _pastes.get('default_expiry', list(allowed_expiry)[0]) expires_at = None if expires_in != 'never': @@ -273,6 +274,7 @@ def create_paste(): '1day': datetime.timedelta(days=1), '1week': datetime.timedelta(weeks=1), '1month': datetime.timedelta(days=30), + '1year': datetime.timedelta(days=365), } delta = delta_map.get(expires_in) if delta is None: @@ -322,15 +324,21 @@ def view_paste_raw(paste_id): paste = _get_paste_or_abort(paste_id) stored = paste['encrypted_data'] - # Plaintext pastes are stored as a JSON object; return the content directly. + # 1. Plaintext paste — Return content directly as text/plain if not re.match(r'^[A-Za-z0-9_-]+:[A-Za-z0-9_-]+$', stored): try: data = json.loads(stored) - return Response(data.get('content', ''), mimetype='text/plain') + return Response(data.get('content', ''), mimetype='text/plain; charset=utf-8') except (json.JSONDecodeError, TypeError): pass - # Encrypted paste — return the raw ciphertext blob for API consumers. + # 2. Encrypted paste — Browsers get a minimal decryptor; API consumers get JSON + accept = request.headers.get('Accept', '') + if 'text/html' in accept: + # Minimal HTML shell that handles decryption for browsers (E2E) + return render_template('raw_decryptor.html', paste=paste) + + # API response return jsonify({ 'id': paste['id'], 'encrypted_data': stored, diff --git a/config.json b/config.json index ba11e66..2eb4d75 100644 --- a/config.json +++ b/config.json @@ -25,14 +25,14 @@ "id_length": 8, "recent_limit": 50, "default_language": "text", - "default_expiry": "never", - "allow_expiry_options": ["never", "1hour", "1day", "1week", "1month"], + "default_expiry": "1year", + "allow_expiry_options": ["1hour", "1day", "1week", "1month", "1year"], "expiry_labels": { - "never": "Never", "1hour": "1 Hour", "1day": "1 Day", "1week": "1 Week", - "1month": "1 Month" + "1month": "1 Month", + "1year": "1 Year" } }, diff --git a/static/js/paste_view.js b/static/js/paste_view.js index 9db9709..eeec6a9 100644 --- a/static/js/paste_view.js +++ b/static/js/paste_view.js @@ -137,15 +137,9 @@ function showError(title, detail) { } function rawView() { - if (!_decryptedPaste) return; - const blob = new Blob([_decryptedPaste.content], { type: 'text/plain; charset=utf-8' }); - const url = URL.createObjectURL(blob); - const a = Object.assign(document.createElement('a'), - { href: url, target: '_blank', rel: 'noopener noreferrer' }); - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - setTimeout(() => URL.revokeObjectURL(url), 10000); + const key = window.location.hash; + const url = window.location.href.split('?')[0].split('#')[0] + '/raw' + key; + window.open(url, '_blank'); } async function copyPaste() { diff --git a/templates/raw_decryptor.html b/templates/raw_decryptor.html new file mode 100644 index 0000000..60675de --- /dev/null +++ b/templates/raw_decryptor.html @@ -0,0 +1,37 @@ + + + + + Raw | {{ cfg.site.name }} + + + +

+    
Decryption Failed — Key missing or incorrect.
+ + + + +