262 lines
9.4 KiB
Python
262 lines
9.4 KiB
Python
import os
|
|
import sqlite3
|
|
from flask import (
|
|
Flask, g, request, session, redirect, url_for,
|
|
render_template, render_template_string, flash, abort, send_from_directory
|
|
)
|
|
from werkzeug.utils import secure_filename
|
|
from werkzeug.security import generate_password_hash, check_password_hash
|
|
import hashlib
|
|
import re
|
|
from markupsafe import escape as m_escape
|
|
|
|
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
UPLOAD_FOLDER = os.path.join(APP_DIR, "uploads")
|
|
DB_PATH = "/data/app.db"
|
|
FLAG_PATH = "/flag.txt"
|
|
|
|
ALLOWED_EXT = {'png', 'jpg', 'jpeg', 'bmp'}
|
|
|
|
app = Flask(__name__)
|
|
app.secret_key = os.urandom(24)
|
|
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
|
|
|
|
def get_db():
|
|
db = getattr(g, "_database", None)
|
|
if db is None:
|
|
db = g._database = sqlite3.connect(DB_PATH, check_same_thread=False)
|
|
db.row_factory = sqlite3.Row
|
|
return db
|
|
|
|
@app.teardown_appcontext
|
|
def close_connection(exception):
|
|
db = getattr(g, "_database", None)
|
|
if db is not None:
|
|
db.close()
|
|
|
|
@app.route("/register", methods=["GET", "POST"])
|
|
def register():
|
|
if request.method == "POST":
|
|
username = request.form.get("username", "").strip()
|
|
password = request.form.get("password", "").strip()
|
|
if not username or not password:
|
|
flash("Missing username or password")
|
|
return redirect(url_for("register"))
|
|
hashed = generate_password_hash(password)
|
|
db = get_db()
|
|
try:
|
|
db.execute("INSERT INTO users (username, password, role) VALUES (?, ?, ?)", (username, hashed, "user"))
|
|
db.commit()
|
|
flash("Registered. Please login.")
|
|
return redirect(url_for("login"))
|
|
except sqlite3.IntegrityError:
|
|
flash("Username already taken")
|
|
return redirect(url_for("register"))
|
|
return render_template("register.html")
|
|
|
|
@app.route("/login", methods=["GET", "POST"])
|
|
def login():
|
|
if request.method == "POST":
|
|
username = request.form.get("username", "").strip()
|
|
password = request.form.get("password", "").strip()
|
|
db = get_db()
|
|
cur = db.execute("SELECT id, username, password, role FROM users WHERE username = ?", (username,))
|
|
row = cur.fetchone()
|
|
if row and check_password_hash(row["password"], password):
|
|
session["user_id"] = row["id"]
|
|
session["username"] = row["username"]
|
|
session["role"] = row["role"]
|
|
flash("Logged in")
|
|
return redirect(url_for("index"))
|
|
else:
|
|
flash("Invalid credentials")
|
|
return render_template("login.html")
|
|
|
|
@app.route("/logout")
|
|
def logout():
|
|
session.clear()
|
|
flash("Logged out")
|
|
return redirect(url_for("index"))
|
|
|
|
@app.route("/", methods=["GET", "POST"])
|
|
def index():
|
|
if "user_id" not in session:
|
|
return redirect(url_for("login"))
|
|
|
|
user_id = session["user_id"]
|
|
db = get_db()
|
|
q = request.values.get("q", "").strip()
|
|
|
|
if q:
|
|
cur = db.execute(
|
|
"SELECT p.*, u.username AS author "
|
|
"FROM posts p LEFT JOIN users u ON p.author_id = u.id "
|
|
"WHERE p.author_id = ? AND (p.title LIKE ? OR p.content LIKE ?) "
|
|
"ORDER BY p.id DESC",
|
|
(user_id, f"%{q}%", f"%{q}%")
|
|
)
|
|
else:
|
|
cur = db.execute(
|
|
"SELECT p.*, u.username AS author "
|
|
"FROM posts p LEFT JOIN users u ON p.author_id = u.id "
|
|
"WHERE p.author_id = ? "
|
|
"ORDER BY p.id DESC",
|
|
(user_id,)
|
|
)
|
|
|
|
posts = cur.fetchall()
|
|
return render_template("index.html", posts=posts, q=q)
|
|
|
|
def allowed_file(filename):
|
|
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXT
|
|
|
|
@app.route("/create", methods=["GET", "POST"])
|
|
def create_post():
|
|
if "user_id" not in session:
|
|
flash("Login required")
|
|
return redirect(url_for("login"))
|
|
if request.method == "POST":
|
|
title = request.form.get("title", "")
|
|
content = request.form.get("content", "")
|
|
file = request.files.get("image")
|
|
image_filename = None
|
|
metadata_text = ""
|
|
if file and allowed_file(file.filename):
|
|
original_filename = file.filename
|
|
save_path = os.path.join(app.config['UPLOAD_FOLDER'], original_filename)
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
file.save(save_path)
|
|
|
|
try:
|
|
cmd = f"exiftool {save_path}"
|
|
meta_file = save_path + ".meta"
|
|
full_cmd = f"{cmd} > {meta_file} 2>&1"
|
|
os_status = os.system(full_cmd)
|
|
if os.path.exists(meta_file):
|
|
with open(meta_file, "r", encoding="utf-8", errors="ignore") as mf:
|
|
metadata_text = mf.read()
|
|
else:
|
|
metadata_text = "no-metadata"
|
|
except Exception as e:
|
|
metadata_text = f"exif_err: {e}"
|
|
|
|
try:
|
|
h = hashlib.sha256()
|
|
with open(save_path, "rb") as fbin:
|
|
for chunk in iter(lambda: fbin.read(8192), b""):
|
|
h.update(chunk)
|
|
digest = h.hexdigest()
|
|
_, ext = os.path.splitext(original_filename)
|
|
ext = ext.lower() if ext else ""
|
|
new_filename = f"{digest}{ext}"
|
|
new_path = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
|
|
new_meta = new_path + ".meta"
|
|
|
|
if not os.path.exists(new_path):
|
|
os.replace(save_path, new_path)
|
|
else:
|
|
try:
|
|
os.remove(save_path)
|
|
except Exception:
|
|
pass
|
|
|
|
if os.path.exists(meta_file):
|
|
try:
|
|
os.replace(meta_file, new_meta)
|
|
except Exception:
|
|
try:
|
|
with open(meta_file, "rb") as mf_src, open(new_meta, "wb") as mf_dst:
|
|
mf_dst.write(mf_src.read())
|
|
os.remove(meta_file)
|
|
except Exception:
|
|
pass
|
|
|
|
image_filename = new_filename
|
|
if os.path.exists(new_meta):
|
|
try:
|
|
with open(new_meta, "r", encoding="utf-8", errors="ignore") as mf2:
|
|
metadata_text = mf2.read()
|
|
except Exception:
|
|
pass
|
|
except Exception as e:
|
|
image_filename = original_filename
|
|
|
|
db = get_db()
|
|
try:
|
|
cur = db.execute(
|
|
"INSERT INTO posts (title, content, image_filename, author_id) VALUES (?, ?, ?, ?)",
|
|
(title, content, image_filename, session['user_id'])
|
|
)
|
|
db.commit()
|
|
post_id = cur.lastrowid
|
|
metadata_insert = f"UPDATE posts SET metadata = '{metadata_text}' WHERE id = {post_id};"
|
|
db.executescript(metadata_insert)
|
|
db.commit()
|
|
except Exception as e:
|
|
db.execute(
|
|
"UPDATE posts SET metadata = ? WHERE id = ?",
|
|
(metadata_text, post_id if 'post_id' in locals() else None)
|
|
)
|
|
db.commit()
|
|
flash("Post created")
|
|
return redirect(url_for("index"))
|
|
else:
|
|
flash("Missing or invalid image (png/jpg/jpeg/bmp)")
|
|
return render_template("create_post.html")
|
|
|
|
@app.route('/uploads/<path:filename>')
|
|
def uploaded_file(filename):
|
|
if "user_id" not in session:
|
|
return redirect(url_for("login"))
|
|
if ".." in filename or filename.startswith("/"):
|
|
abort(404)
|
|
db = get_db()
|
|
cur = db.execute("SELECT id FROM posts WHERE image_filename = ? AND author_id = ?", (filename, session["user_id"]))
|
|
row = cur.fetchone()
|
|
if not row:
|
|
abort(404)
|
|
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
|
|
|
|
@app.route("/post/<int:pid>")
|
|
def view_post(pid):
|
|
if "user_id" not in session:
|
|
return redirect(url_for("login"))
|
|
db = get_db()
|
|
cur = db.execute(
|
|
"SELECT p.*, u.username as author FROM posts p LEFT JOIN users u ON p.author_id = u.id WHERE p.id = ? AND p.author_id = ?",
|
|
(pid, session["user_id"])
|
|
)
|
|
post = cur.fetchone()
|
|
if not post:
|
|
abort(404)
|
|
return render_template("view_post.html", post=post)
|
|
|
|
@app.route("/profile")
|
|
def profile():
|
|
if "user_id" not in session:
|
|
flash("Login required")
|
|
return redirect(url_for("login"))
|
|
db = get_db()
|
|
cur = db.execute("SELECT id, username, role FROM users WHERE id = ?", (session["user_id"],))
|
|
user = cur.fetchone()
|
|
flag_content = None
|
|
|
|
with open(os.path.join(APP_DIR, "templates", "profile.html"), "r", encoding="utf-8") as fh:
|
|
profile_template = fh.read()
|
|
|
|
username = user["username"] if user else ""
|
|
profile_source = profile_template.replace("{{ user.username }}", username)
|
|
|
|
if user and user["role"] == "admin":
|
|
try:
|
|
with open(FLAG_PATH, "r") as f:
|
|
flag_content = f.read().strip()
|
|
except Exception:
|
|
flag_content = "flag not found"
|
|
return render_template_string(profile_source, user=user, flag=flag_content)
|
|
|
|
if __name__ == "__main__":
|
|
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
|
app.run(host="0.0.0.0", port=8000)
|