From c76ff11c26e7df711f99f334c24c4a03a513867c Mon Sep 17 00:00:00 2001 From: spitkov Date: Sat, 14 Sep 2024 20:36:31 +0200 Subject: [PATCH] i dont even remember what i did i just gonn apsuh because its not up to date and i dproably made some curical improvements that i will realsie ver time nd i cant speel because im alread yhigh --- app.py | 165 ++++++++++------ templates/index.html | 149 +++++++++++++- templates/password_prompt.html | 9 +- templates/user_files.html | 348 +++++++++++++++++++++------------ 4 files changed, 473 insertions(+), 198 deletions(-) diff --git a/app.py b/app.py index 8d8b90e..97fb6f6 100644 --- a/app.py +++ b/app.py @@ -230,47 +230,45 @@ def serve_user_page(username, filename=None): current_folder=current_folder) @app.route('/', methods=['GET', 'POST']) -def redirect_vanity(vanity): +@app.route('//', methods=['GET', 'POST']) +@app.route('//download', methods=['GET', 'POST']) +@app.route('//download/', methods=['GET', 'POST']) +def redirect_vanity(vanity, password=None): db = get_db() cursor = db.cursor() cursor.execute("SELECT content.*, users.username FROM content LEFT JOIN users ON content.user_id = users.id WHERE content.vanity = ?", (vanity,)) content = cursor.fetchone() - print(f"Fetched content for vanity {vanity}: {content}") - if content: - content_type, content_data, created_at, user_id, is_private, password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7] + content_type, content_data, created_at, user_id, is_private, stored_password, username = content[1], content[2], content[3], content[4], content[5], content[6], content[7] - print(f"Debug - Vanity: {vanity}, Type: {content_type}, Is Private: {is_private}, Password: {password}") - print(f"User ID: {user_id}, Username: {username}") + if is_private and stored_password: + if password: + if password != stored_password: + return "Incorrect password", 403 + elif request.method == 'POST': + entered_password = request.form.get('password') + if entered_password != stored_password: + return render_template('password_prompt.html', vanity=vanity, error="Incorrect password") + else: + return render_template('password_prompt.html', vanity=vanity, error=None) + + # Remove '/download' from the content_data if present + content_data = content_data.replace('/download', '') if content_type == 'url': return redirect(content_data) elif content_type == 'file': file_path = os.path.join(app.config['UPLOAD_FOLDER'], content_data) if os.path.exists(file_path): - return send_file(file_path) + if 'download' in request.path: + return send_file(file_path, as_attachment=True) + else: + return send_file(file_path) else: return "File not found", 404 elif content_type == 'pastebin': - if is_private: - if request.method == 'POST': - entered_password = request.form.get('password') - print(f"Entered password: {entered_password}") - print(f"Stored password: {password}") - if password and entered_password: - if entered_password == password: - print("Password match!") - return render_pastebin(content_data, created_at, user_id, username, vanity, is_private) - else: - print("Password mismatch!") - return render_template('password_prompt.html', vanity=vanity, error="Incorrect password") - else: - print(f"Missing password. Entered: {entered_password}, Stored: {password}") - return render_template('password_prompt.html', vanity=vanity, error="An error occurred. Please try again.") - return render_template('password_prompt.html', vanity=vanity) - else: - return render_pastebin(content_data, created_at, user_id, username, vanity, is_private) + return render_pastebin(content_data, created_at, user_id, username, vanity, is_private) return "Not found", 404 @@ -295,17 +293,33 @@ def render_pastebin(content_data, created_at, user_id, username, vanity, is_priv vanity=vanity, is_private=is_private) -@app.route('//raw') -def raw_vanity(vanity): +@app.route('//raw', methods=['GET', 'POST']) +@app.route('//raw/', methods=['GET']) +def raw_vanity(vanity, password=None): db = get_db() cursor = db.cursor() cursor.execute("SELECT * FROM content WHERE vanity = ? AND type = 'pastebin'", (vanity,)) - target = cursor.fetchone() + content = cursor.fetchone() - if target: - return target[2], 200, {'Content-Type': 'text/plain; charset=utf-8'} + if content: + content_type, content_data, created_at, user_id, is_private, stored_password = content[1], content[2], content[3], content[4], content[5], content[6] + + if is_private and stored_password: + if password: + if password != stored_password: + return "Incorrect password", 403 + elif request.method == 'POST': + entered_password = request.form.get('password') + if entered_password != stored_password: + return render_template('password_prompt.html', vanity=vanity, error="Incorrect password", raw=True) + else: + return render_template('password_prompt.html', vanity=vanity, error=None, raw=True) + + # Remove '/download' from the content_data if present + content_data = content_data.replace('/download', '') + + return content_data, 200, {'Content-Type': 'text/plain; charset=utf-8'} return 'Not Found', 404 - # Replace the LoginForm and RegistrationForm classes with simple classes class LoginForm: def __init__(self, username, password, remember): @@ -445,19 +459,34 @@ def toggle_index(username): @login_required def upload_user_file(username): if current_user.username != username: - return "Unauthorized", 401 + return jsonify({"success": False, "error": "Unauthorized"}), 401 subpath = request.form.get('subpath', '').rstrip('/') if 'file' not in request.files: - return 'No file part', 400 + return jsonify({"success": False, "error": 'No file part'}), 400 file = request.files['file'] if file.filename == '': - return 'No selected file', 400 + return jsonify({"success": False, "error": 'No selected file'}), 400 if file: filename = secure_filename(file.filename) file_path = os.path.join(app.config['UPLOAD_FOLDER'], username, subpath, filename) os.makedirs(os.path.dirname(file_path), exist_ok=True) - file.save(file_path) - return redirect(url_for('user_files', username=username, subpath=subpath)) + + # Use chunked upload + chunk_size = 4096 # 4KB chunks + try: + with open(file_path, 'wb') as f: + while True: + chunk = file.stream.read(chunk_size) + if not chunk: + break + f.write(chunk) + + return jsonify({"success": True, "filename": filename}), 200 + except Exception as e: + app.logger.error(f"Error uploading file: {str(e)}") + return jsonify({"success": False, "error": str(e)}), 500 + + return jsonify({"success": False, "error": "File upload failed"}), 500 @app.route('/dash//delete/', methods=['POST']) @login_required @@ -639,8 +668,8 @@ def shorten_url(): (vanity, 'url', long_url, datetime.now(), user_id)) db.commit() - short_url = f"{request.host_url}{vanity}" - return jsonify({'success': True, 'vanity': vanity, 'short_url': short_url}), 200 + # Return only the vanity code, not the full URL + return jsonify({'success': True, 'vanity': vanity}), 200 except Exception as e: print("Exception occurred:", str(e)) return jsonify({'success': False, 'error': str(e)}), 400 @@ -676,7 +705,7 @@ def edit_content(vanity): return jsonify({'success': False, 'error': 'Unsupported content type for editing'}), 400 -@app.route('/edit_password/', methods=['GET', 'POST']) +@app.route('/edit_password/', methods=['POST']) @login_required def edit_password(vanity): db = get_db() @@ -687,21 +716,20 @@ def edit_password(vanity): if not content or content[4] != current_user.id: return jsonify({'success': False, 'error': 'Unauthorized'}), 401 - if request.method == 'POST': - data = request.get_json() - action = data.get('action') - if action == 'update': - new_password = data.get('new_password') - cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity)) - db.commit() - return jsonify({'success': True}) - elif action == 'remove': - cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,)) - db.commit() - return jsonify({'success': True}) - return jsonify({'success': False, 'error': 'Invalid action'}) + data = request.get_json() + action = data.get('action') + if action == 'update': + new_password = data.get('new_password') + if not is_valid_password(new_password): + return jsonify({'success': False, 'error': 'Invalid password'}), 400 + cursor.execute("UPDATE content SET password = ?, is_private = 1 WHERE vanity = ?", (new_password, vanity)) + elif action == 'remove': + cursor.execute("UPDATE content SET is_private = 0, password = NULL WHERE vanity = ?", (vanity,)) + else: + return jsonify({'success': False, 'error': 'Invalid action'}), 400 - return render_template('edit_password.html', vanity=vanity) + db.commit() + return jsonify({'success': True}) @app.route('/delete/content/', methods=['POST']) @login_required @@ -725,7 +753,7 @@ def delete_content(vanity): return jsonify({'success': True}), 200 -@app.route('//info') +@app.route('//info', methods=['GET', 'POST']) def content_info(vanity): db = get_db() cursor = db.cursor() @@ -733,7 +761,15 @@ def content_info(vanity): content = cursor.fetchone() if content: - content_type, content_data, created_at, user_id = content[1], content[2], content[3], content[4] + content_type, content_data, created_at, user_id, is_private, password = content[1], content[2], content[3], content[4], content[5], content[6] + + if is_private and password: + if request.method == 'POST': + entered_password = request.form.get('password') + if entered_password != password: + return render_template('password_prompt.html', vanity=vanity, error="Incorrect password") + else: + return render_template('password_prompt.html', vanity=vanity, error=None) username = get_username(user_id) @@ -753,7 +789,8 @@ def content_info(vanity): 'created_at': created_at, 'username': username, 'file_size': file_size, - 'is_media': is_media + 'is_media': is_media, + 'is_private': is_private } return render_template('content_info.html', info=info) @@ -1012,19 +1049,26 @@ def upload_file(): user_id = current_user.id if current_user.is_authenticated else None + password = request.form.get('password') + is_private = 1 if password else 0 + db = get_db() cursor = db.cursor() - cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id) VALUES (?, ?, ?, ?, ?)", - (vanity_with_extension, 'file', new_filename, datetime.now(), user_id)) + cursor.execute("INSERT INTO content (vanity, type, data, created_at, user_id, is_private, password) VALUES (?, ?, ?, ?, ?, ?, ?)", + (vanity_with_extension, 'file', new_filename, datetime.now(), user_id, is_private, password)) db.commit() short_url = url_for('redirect_vanity', vanity=vanity_with_extension, _external=True) + # Remove '/download' suffix if it exists + short_url = short_url.replace('/download', '') + download_url = short_url + '/download' deletion_url = url_for('delete_content', vanity=vanity_with_extension, _external=True) return jsonify({ 'success': True, 'vanity': vanity_with_extension, 'url': short_url, + 'download_url': download_url, 'deletion_url': deletion_url, 'filename': new_filename }) @@ -1033,6 +1077,11 @@ def upload_file(): return jsonify({'success': False, 'error': 'Unknown error occurred'}), 500 +# Add this function to validate passwords +def is_valid_password(password): + banned_passwords = ['info', 'download'] + return password not in banned_passwords + if __name__ == '__main__': # Start the cleanup thread cleanup_thread = threading.Thread(target=delete_old_files) diff --git a/templates/index.html b/templates/index.html index 0d1f97b..e49a49a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -205,6 +205,59 @@ button:hover { background-color: #45a049; } + + .file-group { + border: 1px solid #4CAF50; + border-radius: 5px; + margin-bottom: 10px; + overflow: hidden; + } + + .file-group-header { + background-color: #4CAF50; + color: white; + padding: 10px; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + } + + .file-group-header span:first-child { + margin-right: 10px; + } + + .file-group-header span:nth-child(2) { + flex-grow: 1; + text-align: left; + } + + .file-group-content { + padding: 10px; + display: none; + } + + .file-group-content.show { + display: block; + } + + .other-links-btn { + background-color: #45a049; + color: white; + border: none; + padding: 5px 10px; + border-radius: 3px; + cursor: pointer; + } + + .other-links { + margin-top: 10px; + display: none; + } + + .other-links.show { + display: block; + } @@ -255,6 +308,15 @@

Drag and drop files here or click to select files

+
+
+ + +
+ +
@@ -367,6 +429,10 @@ document.getElementById('passwordField').style.display = this.checked ? 'block' : 'none'; }); + document.getElementById('fileIsPrivate').addEventListener('change', function() { + document.getElementById('filePasswordField').style.display = this.checked ? 'block' : 'none'; + }); + function uploadText() { const content = document.getElementById('textContent').value; const isPrivate = document.getElementById('isPrivate').checked; @@ -407,6 +473,8 @@ const uploadArea = document.getElementById('uploadArea'); const fileInput = document.getElementById('fileInput'); const progressBar = document.getElementById('progressBar'); + const selectedFiles = document.getElementById('selectedFiles'); + let filesToUpload = []; uploadArea.addEventListener('click', () => fileInput.click()); uploadArea.addEventListener('dragover', (e) => { @@ -424,14 +492,33 @@ fileInput.addEventListener('change', (e) => handleFiles(e.target.files)); function handleFiles(files) { - for (let file of files) { - uploadFile(file); - } + filesToUpload = Array.from(files); + updateSelectedFilesList(); } - function uploadFile(file) { + function updateSelectedFilesList() { + selectedFiles.innerHTML = filesToUpload.map(file => `
${file.name}
`).join(''); + } + + function uploadSelectedFiles() { + const isPrivate = document.getElementById('fileIsPrivate').checked; + const password = isPrivate ? document.getElementById('filePassword').value : null; + + filesToUpload.forEach(file => uploadFile(file, isPrivate, password)); + } + + function uploadFile(file, isPrivate, password) { + if (isPrivate && (password === 'info' || password === 'download')) { + document.getElementById('fileResult').innerHTML += `Error: Password cannot be 'info' or 'download'
`; + return; + } + const formData = new FormData(); formData.append('file', file); + + if (isPrivate && password) { + formData.append('password', password); + } const xhr = new XMLHttpRequest(); xhr.open('POST', '/upload/file', true); @@ -448,7 +535,37 @@ if (xhr.status === 200) { const response = JSON.parse(xhr.responseText); if (response.success) { - document.getElementById('fileResult').innerHTML += `File uploaded: ${response.url}
`; + let resultHtml = ` +
+
+ + ${file.name} + +
+
+

File uploaded: ${response.url}

+ +
+
+ `; + + document.getElementById('fileResult').innerHTML += resultHtml; } else { document.getElementById('fileResult').innerHTML += `Error: ${response.error}
`; } @@ -469,6 +586,26 @@ xhr.send(formData); } + function toggleFileGroup(header) { + const content = header.nextElementSibling; + const arrow = header.querySelector('span:first-child'); + const filename = header.querySelector('span:nth-child(2)'); + content.classList.toggle('show'); + if (content.classList.contains('show')) { + arrow.textContent = '▼'; + filename.style.display = 'inline'; + } else { + arrow.textContent = '▶'; + filename.style.display = 'inline'; + } + } + + function toggleOtherLinks(event, button) { + event.stopPropagation(); + const otherLinks = button.closest('.file-group-header').nextElementSibling.querySelector('.other-links'); + otherLinks.classList.toggle('show'); + } + function shortenUrl() { const url = document.getElementById('urlInput').value; @@ -487,7 +624,7 @@ }) .then(data => { if (data.success) { - const shortUrl = `${window.location.origin}/${data.short_url}`; + const shortUrl = `${window.location.origin}/${data.vanity}`; document.getElementById('urlResult').innerHTML = `URL shortened. Access it ${shortUrl}`; } else { document.getElementById('urlResult').innerHTML = `Error: ${data.error}`; diff --git a/templates/password_prompt.html b/templates/password_prompt.html index ea06ea7..d973d71 100644 --- a/templates/password_prompt.html +++ b/templates/password_prompt.html @@ -78,12 +78,9 @@
Incorrect password. Please try again.
-
- -
- - -
+ + +
diff --git a/templates/user_files.html b/templates/user_files.html index fa8a277..a75b5df 100644 --- a/templates/user_files.html +++ b/templates/user_files.html @@ -66,6 +66,8 @@ .file-list { list-style-type: none; padding: 0; + position: relative; + z-index: 1; } .file-item { background-color: var(--highlight-bg); @@ -75,6 +77,8 @@ display: flex; align-items: center; justify-content: space-between; + position: relative; + z-index: 2; } .file-icon { margin-right: 10px; @@ -250,7 +254,7 @@ background-color: var(--bg-color); min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; + z-index: 1000; border-radius: 4px; } @@ -310,11 +314,10 @@ .dropdown-content { display: none; position: absolute; - right: 0; background-color: var(--bg-color); min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; + z-index: 1000; border-radius: 4px; overflow: hidden; } @@ -398,7 +401,7 @@ background-color: var(--bg-color); min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; + z-index: 1000; right: 0; border-radius: 4px; } @@ -624,11 +627,10 @@ .dropdown-content { display: none; position: absolute; - right: 0; background-color: var(--bg-color); min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; + z-index: 1000; border-radius: 4px; } @@ -647,63 +649,6 @@ display: block; } - .file-item, .upload-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 10px; - background-color: var(--highlight-bg); - margin-bottom: 5px; - border-radius: 4px; - } - - .file-name, .upload-info { - flex-grow: 1; - margin-right: 10px; - word-break: break-all; - } - - .dropdown { - position: relative; - display: inline-block; - } - - .dropbtn { - background-color: transparent; - color: var(--text-color); - padding: 5px; - font-size: 16px; - border: none; - cursor: pointer; - } - - .dropdown-content { - display: none; - position: absolute; - right: 0; - background-color: var(--bg-color); - min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; - border-radius: 4px; - } - - .dropdown-content a { - color: var(--text-color); - padding: 12px 16px; - text-decoration: none; - display: block; - } - - .dropdown-content a:hover { - background-color: var(--highlight-bg); - } - - .dropdown:hover .dropdown-content { - display: block; - } - - /* Ensure the dropdown is always on top */ .file-item, .upload-item { position: relative; } @@ -725,15 +670,15 @@ .file-item:last-child .dropdown-content, .upload-item:last-child .dropdown-content { - bottom: 100%; - top: auto; + bottom: auto; + top: 100%; } /* Media query for smaller screens */ @media screen and (max-height: 600px) { .dropdown-content { - bottom: 100%; - top: auto; + bottom: auto; + top: 100%; } } @@ -773,9 +718,10 @@ background-color: var(--bg-color); min-width: 160px; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - z-index: 1; + z-index: 9999; border-radius: 4px; right: 0; + margin-top: 2px; /* Add a small gap between button and dropdown */ } .dropdown-content a { @@ -793,24 +739,131 @@ display: block; } - /* New styles for adaptive positioning */ - .file-list, .upload-list { + /* Ensure the dropdown is always on top */ + .file-item:hover, .upload-item:hover { + z-index: 1001; + } + + .dropdown:hover { + z-index: 1002; + } + + /* Styles for the collapsible file groups */ + .file-group { + margin-bottom: 10px; + border: 1px solid var(--highlight-border); + border-radius: 4px; + overflow: hidden; + } + + .file-group-header { + display: flex; + align-items: center; + background-color: var(--highlight-bg); + padding: 10px; + cursor: pointer; + } + + .file-group-arrow { + margin-right: 10px; + font-size: 16px; + transition: transform 0.3s; + } + + .file-group-arrow.collapsed { + transform: rotate(-90deg); + } + + .file-group-content.expanded { + display: block; + } + + .file-group-links { + margin-top: 10px; + } + + .file-group-link { + display: block; + margin-bottom: 5px; + } + + .file-group-link a { + color: var(--text-color); + text-decoration: none; + } + + .file-group-link a:hover { + text-decoration: underline; + } + + .file-list { position: relative; + z-index: 1; + } + + .file-item { + position: relative; + z-index: 2; + } + + .dropdown { + position: relative; + z-index: 1000; /* Increased z-index */ + } + + .dropbtn { + background-color: transparent; + color: var(--text-color); + padding: 5px; + font-size: 16px; + border: none; + cursor: pointer; } .dropdown-content { - bottom: auto; + display: none; + position: absolute; + right: 0; top: 100%; + background-color: var(--bg-color); + min-width: 160px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + z-index: 1001; /* Increased z-index */ + border-radius: 4px; + overflow: visible; /* Changed from hidden to visible */ + } + + .dropdown-content a { + color: var(--text-color); + padding: 12px 16px; + text-decoration: none; + display: block; + } + + .dropdown-content a:hover { + background-color: var(--highlight-bg); } .dropdown:hover .dropdown-content { display: block; } - /* JavaScript will add this class when needed */ - .dropdown-content.above { - bottom: 100%; - top: auto; + /* Ensure the dropdown is always on top */ + .file-item:hover, + .upload-item:hover { + z-index: 1002; /* Increased z-index */ + } + + .dropdown:hover { + z-index: 1003; /* Increased z-index */ + } + + /* Remove any max-height or overflow properties on parent containers */ + .tab-content, + .file-list, + .upload-list { + overflow: visible; + max-height: none; } @@ -920,12 +973,10 @@ Edit {% endif %} Delete - {% if upload.type == 'pastebin' %} - {% if upload.is_private %} - Edit Password - {% else %} - Add Password - {% endif %} + {% if upload.is_private %} + Edit Password + {% else %} + Add Password {% endif %} @@ -971,7 +1022,7 @@ -