roundabout,
created on Monday, 4 December 2023, 17:41:44 (1701711704),
received on Wednesday, 31 July 2024, 06:54:38 (1722408878)
Author identity: vlad <vlad.muntoiu@gmail.com>
ea7cf8e86b9a30b54e7761eccaf146fe06a5561b
app.py
@@ -24,6 +24,7 @@ import config
app = flask.Flask(__name__)
from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()
app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
@@ -41,7 +42,8 @@ def gitCommand(repo, data, *args):
command = ["git", *args]
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE)
proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
print(command)
if data:
@@ -106,7 +108,8 @@ with app.app_context():
avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH))
if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"):
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), write_to="/tmp/roundabout-avatar.png")
cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName),
write_to="/tmp/roundabout-avatar.png")
avatar = Image.open("/tmp/roundabout-avatar.png")
else:
avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName))
@@ -188,7 +191,8 @@ def getVisibility(username, repository):
import gitHTTP
def humanSize(value, decimals=2, scale=1024, units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
def humanSize(value, decimals=2, scale=1024,
units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
for unit in units:
if value < scale:
break
@@ -196,7 +200,7 @@ def humanSize(value, decimals=2, scale=1024, units=("B", "kiB", "MiB", "GiB", "T
if int(value) == value:
# do not return decimals, if the value is already round
return int(value), unit
return round(value * 10**decimals) / 10**decimals, unit
return round(value * 10 ** decimals) / 10 ** decimals, unit
def guessMIME(path):
@@ -269,44 +273,57 @@ def login():
if user and bcrypt.check_password_hash(user.passwordHashed, password):
flask.session["username"] = user.username
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"), category="success")
flask.flash(
Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
category="success")
return flask.redirect("/", code=303)
elif not user:
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"), category="alert")
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
category="alert")
return flask.render_template("login.html")
else:
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"), category="error")
flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
category="error")
return flask.render_template("login.html")
if "signup" in flask.request.form:
username = flask.request.form["username"]
password = flask.request.form["password"]
password2 = flask.request.form["password2"]
email = flask.request.form.get("email")
email2 = flask.request.form.get("email2") # repeat email is a honeypot
email2 = flask.request.form.get("email2") # repeat email is a honeypot
name = flask.request.form.get("name")
if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
flask.flash(Markup("<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"), category="error")
flask.flash(Markup(
"<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
category="error")
return flask.render_template("login.html")
if username in config.RESERVED_NAMES:
flask.flash(Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"), category="error")
flask.flash(
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
category="error")
return flask.render_template("login.html")
userCheck = User.query.filter_by(username=username).first()
if userCheck:
flask.flash(Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"), category="error")
flask.flash(
Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
category="error")
return flask.render_template("login.html")
if password2 != password:
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"), category="error")
flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
category="error")
return flask.render_template("login.html")
user = User(username, password, email, name)
db.session.add(user)
db.session.commit()
flask.session["username"] = user.username
flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"), category="success")
flask.flash(Markup(
f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
category="success")
return flask.redirect("/", code=303)
@@ -320,8 +337,8 @@ def newRepo():
if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
flask.flash(Markup(
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
category="error")
"<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
category="error")
return flask.render_template("new-repo.html")
user = User.query.filter_by(username=flask.session.get("username")).first()
@@ -331,9 +348,11 @@ def newRepo():
db.session.commit()
if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
subprocess.run(["git", "init", repo.name], cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
subprocess.run(["git", "init", repo.name],
cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"), category="success")
flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
category="success")
return flask.redirect(repo.route, code=303)
@@ -358,7 +377,8 @@ def repositoryIndex(username, repository):
@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
def repositoryRaw(username, repository, branch, subpath):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username, repository) is not None):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username,
repository) is not None):
flask.abort(403)
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
@@ -392,7 +412,8 @@ def userAvatar(username):
@app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
@app.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
def repositoryTree(username, repository, branch, subpath):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
repository) is not None):
flask.abort(403)
serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
@@ -409,15 +430,23 @@ def repositoryTree(username, repository, branch, subpath):
if repo.heads:
repoData.defaultBranch = repo.heads[0].name
else:
return flask.render_template("empty.html", remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
return flask.render_template("empty.html",
remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
if not branch:
branch = repoData.defaultBranch
return flask.redirect(f"./{branch}", code=302)
if branch.startswith("tag:"):
ref = f"tags/{branch[4:]}"
if branch.startswith("~"):
ref = branch[1:]
else:
try:
repo.git.checkout("-f", branch)
except git.exc.GitCommandError:
return flask.render_template("not-found.html"), 404
ref = f"refs/{branch}"
try:
repo.git.checkout("-f", branch)
except git.exc.GitCommandError:
return flask.render_template("not-found.html"), 404
branches = repo.heads
@@ -520,7 +549,8 @@ def repositoryTree(username, repository, branch, subpath):
@app.route("/<username>/<repository>/forum/")
def repositoryForum(username, repository):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
repository) is not None):
flask.abort(403)
return flask.render_template("repo-forum.html", username=username, repository=repository)
@@ -528,7 +558,8 @@ def repositoryForum(username, repository):
@app.route("/<username>/<repository>/branches/")
def repositoryBranches(username, repository):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
repository) is not None):
flask.abort(403)
return flask.render_template("repo-branches.html", username=username, repository=repository)
@@ -536,7 +567,8 @@ def repositoryBranches(username, repository):
@app.route("/<username>/<repository>/log/")
def repositoryLog(username, repository):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, repository) is not None):
if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
repository) is not None):
flask.abort(403)
return flask.render_template("repo-log.html", username=username, repository=repository)
@@ -569,3 +601,6 @@ def e403(error):
def e418(error):
return flask.render_template("teapot.html"), 418
if __name__ == "__main__":
app.run(debug=True, port=8080, host="0.0.0.0")
gitHTTP.py
@@ -29,6 +29,7 @@ def verifyPassword(username, password):
return False
@app.route("/<username>/<repository>/git-upload-pack", methods=["POST"])
@app.route("/git/<username>/<repository>/git-upload-pack", methods=["POST"])
@auth.login_required(optional=True)
def gitUploadPack(username, repository):
@@ -43,6 +44,7 @@ def gitUploadPack(username, repository):
return flask.Response(text, content_type="application/x-git-upload-pack-result")
@app.route("/<username>/<repository>/git-receive-pack", methods=["POST"])
@app.route("/git/<username>/<repository>/git-receive-pack", methods=["POST"])
@auth.login_required
def gitReceivePack(username, repository):
@@ -70,7 +72,8 @@ def gitReceivePack(username, repository):
return flask.Response(text, content_type="application/x-git-receive-pack-result")
@app.route("/git/<username>/<repository>/info/refs", methods=["GET"])
@app.route("/<username>/<repository>/info/refs", methods=["GET", "POST"])
@app.route("/git/<username>/<repository>/info/refs", methods=["GET", "POST"])
@auth.login_required(optional=True)
def gitInfoRefs(username, repository):
serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git")