Files
ALPHA-TRAIN2/services/rusgram/content/content.py
T
2026-08-26 12:53:27 +03:00

100 lines
4.1 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/python3
from flask import Flask, render_template, request, redirect, abort
import os
import requests as r
app = Flask(__name__, template_folder='files')
database_node_port = 5002
content_node_port = 5004
database_node = "database:" + str(database_node_port)
INTERNAL = os.environ.get("RUSGRAM_INTERNAL", "e9b2f6d14a8c7035e1d0a6b8c4f2e7a9")
DBH = {"X-Internal": INTERNAL}
# PATCH V1/V5: template_folder ('files') — это тот же том, где лежит db/users/<логин>.
# Раньше render_template(path) с путём из URL позволял отрендерить файл юзера
# как Jinja-шаблон => SSTI => RCE. Рендерим только реально существующие страницы.
ALLOWED_TEMPLATES = {"index.html", "login.html", "register.html", "specific.html"}
# PATCH V8: сколько картинок реально есть в db/images
IMG_MIN, IMG_MAX = 1, 16
def get_login():
"""Возвращает имя юзера или None. Никогда не бросает исключение."""
session = request.cookies.get("session")
if session is None:
return None
parts = session.split("||")
if len(parts) != 2: # PATCH V8: раньше тут был ValueError -> 500
return None
username, password = parts
if not username:
return None
try:
resp = r.get(f"http://{database_node}/users/{username}/password",
headers=DBH, timeout=5)
if resp.status_code != 200: # PATCH V8: раньше KeyError -> 500
return None
if resp.json().get("data") != password:
return None
except (r.RequestException, ValueError):
return None
return username
def _field(username, name):
try:
resp = r.get(f"http://{database_node}/users/{username}/{name}",
headers=DBH, timeout=5)
return resp.json()["data"] if resp.status_code == 200 else ""
except (r.RequestException, ValueError, KeyError):
return ""
@app.route('/<path:path>', methods=["GET", "POST"])
def route(path):
if path == "index.html":
if (username := get_login()) is None:
return redirect("/login.html", code=302)
imgs = list()
for i in range(IMG_MIN, IMG_MAX + 1):
try:
imgs.append(r.get(f"http://{database_node}/images/{i}",
headers=DBH, timeout=5).json()["data"])
except (r.RequestException, ValueError, KeyError):
continue
imgs[-1]["id"] = str(i).zfill(2)
user = dict()
user["first_name"] = _field(username, "first_name")
user["second_name"] = _field(username, "second_name")
user["email"] = _field(username, "email")
user["msg"] = _field(username, "msg")
return render_template("index.html", data=imgs, user=user)
elif path == "specific.html":
if get_login() is None:
return redirect("/login.html", code=302)
# PATCH V8: img приходит из URL. Раньше любое нечисловое/вне диапазона
# значение давало 500, а незакавыченная подстановка {{img['id']}} внутрь
# <script> в шаблоне позволяла подмешать JS.
try:
img_id = int(request.args.get('img', ''))
except (TypeError, ValueError):
abort(404)
if not (IMG_MIN <= img_id <= IMG_MAX):
abort(404)
try:
img = r.get(f"http://{database_node}/images/{img_id}",
headers=DBH, timeout=5).json()["data"]
except (r.RequestException, ValueError, KeyError):
abort(404)
img["id"] = str(img_id).zfill(2)
return render_template("specific.html", img=img)
else:
# PATCH V1/V5: всё, чего нет в белом списке, до Jinja не доходит.
if path not in ALLOWED_TEMPLATES:
abort(404)
return render_template(path)
if __name__ == '__main__':
app.run(host="0.0.0.0", port=content_node_port)