app.py
    
    Python script, ASCII text executable
        
            1
            import os 
        
            2
            import random 
        
            3
            import subprocess 
        
            4
            from functools import wraps 
        
            5
             
        
            6
            import cairosvg 
        
            7
            import flask 
        
            8
            from flask_sqlalchemy import SQLAlchemy 
        
            9
            import git 
        
            10
            import mimetypes 
        
            11
            import magic 
        
            12
            from flask_bcrypt import Bcrypt 
        
            13
            from markupsafe import escape, Markup 
        
            14
            from flask_migrate import Migrate 
        
            15
            from datetime import datetime 
        
            16
            from enum import Enum 
        
            17
            import shutil 
        
            18
            from PIL import Image 
        
            19
            from cairosvg import svg2png 
        
            20
            import platform 
        
            21
             
        
            22
            import config 
        
            23
             
        
            24
            app = flask.Flask(__name__) 
        
            25
             
        
            26
            from flask_httpauth import HTTPBasicAuth 
        
            27
             
        
            28
            auth = HTTPBasicAuth() 
        
            29
             
        
            30
            app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI 
        
            31
            app.config["SECRET_KEY"] = config.DB_PASSWORD 
        
            32
            app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 
        
            33
            db = SQLAlchemy(app) 
        
            34
            bcrypt = Bcrypt(app) 
        
            35
            migrate = Migrate(app, db) 
        
            36
             
        
            37
             
        
            38
            def gitCommand(repo, data, *args): 
        
            39
                if not os.path.isdir(repo): 
        
            40
                    raise FileNotFoundError("Repo not found") 
        
            41
                env = os.environ.copy() 
        
            42
             
        
            43
                command = ["git", *args] 
        
            44
             
        
            45
                proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE, 
        
            46
                                        stdin=subprocess.PIPE) 
        
            47
                print(command) 
        
            48
             
        
            49
                if data: 
        
            50
                    proc.stdin.write(data) 
        
            51
             
        
            52
                out, err = proc.communicate() 
        
            53
                return out 
        
            54
             
        
            55
             
        
            56
            def onlyChars(string, chars): 
        
            57
                for i in string: 
        
            58
                    if i not in chars: 
        
            59
                        return False 
        
            60
                return True 
        
            61
             
        
            62
             
        
            63
            with app.app_context(): 
        
            64
                class RepoAccess(db.Model): 
        
            65
                    id = db.Column(db.Integer, primary_key=True) 
        
            66
                    userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            67
                    repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 
        
            68
                    accessLevel = db.Column(db.SmallInteger(), nullable=False)  # 0 read-only, 1 read-write, 2 admin 
        
            69
             
        
            70
                    user = db.relationship("User", back_populates="repoAccess") 
        
            71
                    repo = db.relationship("Repo", back_populates="repoAccess") 
        
            72
             
        
            73
                    __table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),) 
        
            74
             
        
            75
                    def __init__(self, user, repo, level): 
        
            76
                        self.userUsername = user.username 
        
            77
                        self.repoRoute = repo.route 
        
            78
                        self.accessLevel = level 
        
            79
             
        
            80
             
        
            81
                class RepoFavourite(db.Model): 
        
            82
                    id = db.Column(db.Integer, primary_key=True) 
        
            83
                    userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            84
                    repoRoute = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 
        
            85
             
        
            86
                    user = db.relationship("User", back_populates="favourites") 
        
            87
                    repo = db.relationship("Repo", back_populates="favourites") 
        
            88
             
        
            89
                    __table_args__ = (db.UniqueConstraint("userUsername", "repoRoute", name="_user_repo_uc"),) 
        
            90
             
        
            91
                    def __init__(self, user, repo): 
        
            92
                        self.userUsername = user.username 
        
            93
                        self.repoRoute = repo.route 
        
            94
             
        
            95
             
        
            96
                class PostVote(db.Model): 
        
            97
                    id = db.Column(db.Integer, primary_key=True) 
        
            98
                    userUsername = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            99
                    postIdentifier = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=False) 
        
            100
                    voteScore = db.Column(db.SmallInteger(), nullable=False) 
        
            101
             
        
            102
                    user = db.relationship("User", back_populates="votes") 
        
            103
                    post = db.relationship("Post", back_populates="votes") 
        
            104
             
        
            105
                    __table_args__ = (db.UniqueConstraint("userUsername", "postIdentifier", name="_user_post_uc"),) 
        
            106
             
        
            107
                    def __init__(self, user, post, score): 
        
            108
                        self.userUsername = user.username 
        
            109
                        self.postIdentifier = post.identifier 
        
            110
                        self.voteScore = score 
        
            111
             
        
            112
             
        
            113
                class User(db.Model): 
        
            114
                    username = db.Column(db.String(32), unique=True, nullable=False, primary_key=True) 
        
            115
                    displayName = db.Column(db.Unicode(128), unique=False, nullable=True) 
        
            116
                    bio = db.Column(db.Unicode(512), unique=False, nullable=True) 
        
            117
                    passwordHashed = db.Column(db.String(60), nullable=False) 
        
            118
                    email = db.Column(db.String(254), nullable=True) 
        
            119
                    company = db.Column(db.Unicode(64), nullable=True) 
        
            120
                    companyURL = db.Column(db.String(256), nullable=True) 
        
            121
                    URL = db.Column(db.String(256), nullable=True) 
        
            122
                    showMail = db.Column(db.Boolean, default=False, nullable=False) 
        
            123
                    location = db.Column(db.Unicode(64), nullable=True) 
        
            124
                    creationDate = db.Column(db.DateTime, default=datetime.utcnow) 
        
            125
             
        
            126
                    repositories = db.relationship("Repo", back_populates="owner") 
        
            127
                    repoAccess = db.relationship("RepoAccess", back_populates="user") 
        
            128
                    votes = db.relationship("PostVote", back_populates="user") 
        
            129
                    favourites = db.relationship("RepoFavourite", back_populates="user") 
        
            130
             
        
            131
                    commits = db.relationship("Commit", back_populates="owner") 
        
            132
                    posts = db.relationship("Post", back_populates="owner") 
        
            133
             
        
            134
                    def __init__(self, username, password, email=None, displayName=None): 
        
            135
                        self.username = username 
        
            136
                        self.passwordHashed = bcrypt.generate_password_hash(password, config.HASHING_ROUNDS).decode("utf-8") 
        
            137
                        self.email = email 
        
            138
                        self.displayName = displayName 
        
            139
             
        
            140
                        # Create the user's directory 
        
            141
                        if not os.path.exists(os.path.join(config.REPOS_PATH, username)): 
        
            142
                            os.makedirs(os.path.join(config.REPOS_PATH, username)) 
        
            143
                        if not os.path.exists(os.path.join(config.USERDATA_PATH, username)): 
        
            144
                            os.makedirs(os.path.join(config.USERDATA_PATH, username)) 
        
            145
             
        
            146
                        avatarName = random.choice(os.listdir(config.DEFAULT_AVATARS_PATH)) 
        
            147
                        if os.path.join(config.DEFAULT_AVATARS_PATH, avatarName).endswith(".svg"): 
        
            148
                            cairosvg.svg2png(url=os.path.join(config.DEFAULT_AVATARS_PATH, avatarName), 
        
            149
                                             write_to="/tmp/roundabout-avatar.png") 
        
            150
                            avatar = Image.open("/tmp/roundabout-avatar.png") 
        
            151
                        else: 
        
            152
                            avatar = Image.open(os.path.join(config.DEFAULT_AVATARS_PATH, avatarName)) 
        
            153
                        avatar.thumbnail(config.AVATAR_SIZE) 
        
            154
                        avatar.save(os.path.join(config.USERDATA_PATH, username, "avatar.png")) 
        
            155
             
        
            156
             
        
            157
                class Repo(db.Model): 
        
            158
                    route = db.Column(db.String(98), unique=True, nullable=False, primary_key=True) 
        
            159
                    ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            160
                    name = db.Column(db.String(64), nullable=False) 
        
            161
                    owner = db.relationship("User", back_populates="repositories") 
        
            162
                    visibility = db.Column(db.SmallInteger(), nullable=False) 
        
            163
                    info = db.Column(db.Unicode(512), nullable=True) 
        
            164
                    URL = db.Column(db.String(256), nullable=True) 
        
            165
                    creationDate = db.Column(db.DateTime, default=datetime.utcnow) 
        
            166
             
        
            167
                    defaultBranch = db.Column(db.String(64), nullable=True, default="") 
        
            168
             
        
            169
                    commits = db.relationship("Commit", back_populates="repo") 
        
            170
                    posts = db.relationship("Post", back_populates="repo") 
        
            171
                    repoAccess = db.relationship("RepoAccess", back_populates="repo") 
        
            172
                    favourites = db.relationship("RepoFavourite", back_populates="repo") 
        
            173
             
        
            174
                    lastPostID = db.Column(db.Integer, nullable=False, default=0) 
        
            175
             
        
            176
                    def __init__(self, owner, name, visibility): 
        
            177
                        self.route = f"/{owner.username}/{name}" 
        
            178
                        self.name = name 
        
            179
                        self.ownerName = owner.username 
        
            180
                        self.owner = owner 
        
            181
                        self.visibility = visibility 
        
            182
             
        
            183
                        # Add the owner as an admin 
        
            184
                        repoAccess = RepoAccess(owner, self, 2) 
        
            185
                        db.session.add(repoAccess) 
        
            186
             
        
            187
             
        
            188
                class Commit(db.Model): 
        
            189
                    identifier = db.Column(db.String(227), unique=True, nullable=False, primary_key=True) 
        
            190
                    sha = db.Column(db.String(128), nullable=False) 
        
            191
                    repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 
        
            192
                    ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            193
                    ownerIdentity = db.Column(db.String(321)) 
        
            194
                    receiveDate = db.Column(db.DateTime, default=datetime.now) 
        
            195
                    authorDate = db.Column(db.DateTime) 
        
            196
                    message = db.Column(db.UnicodeText) 
        
            197
                    repo = db.relationship("Repo", back_populates="commits") 
        
            198
                    owner = db.relationship("User", back_populates="commits") 
        
            199
             
        
            200
                    def __init__(self, sha, owner, repo, date, message, ownerIdentity): 
        
            201
                        self.identifier = f"{repo.route}/{sha}" 
        
            202
                        self.sha = sha 
        
            203
                        self.repoName = repo.route 
        
            204
                        self.repo = repo 
        
            205
                        self.ownerName = owner.username 
        
            206
                        self.owner = owner 
        
            207
                        self.authorDate = datetime.fromtimestamp(int(date)) 
        
            208
                        self.message = message 
        
            209
                        self.ownerIdentity = ownerIdentity 
        
            210
             
        
            211
             
        
            212
                class Post(db.Model): 
        
            213
                    identifier = db.Column(db.String(109), unique=True, nullable=False, primary_key=True) 
        
            214
                    number = db.Column(db.Integer, nullable=False) 
        
            215
                    repoName = db.Column(db.String(98), db.ForeignKey("repo.route"), nullable=False) 
        
            216
                    ownerName = db.Column(db.String(32), db.ForeignKey("user.username"), nullable=False) 
        
            217
                    votes = db.relationship("PostVote", back_populates="post") 
        
            218
                    voteSum = db.Column(db.Integer, nullable=False, default=0) 
        
            219
             
        
            220
                    parentID = db.Column(db.String(109), db.ForeignKey("post.identifier"), nullable=True) 
        
            221
                    state = db.Column(db.SmallInteger, nullable=True, default=1) 
        
            222
             
        
            223
                    date = db.Column(db.DateTime, default=datetime.now) 
        
            224
                    lastUpdated = db.Column(db.DateTime, default=datetime.now) 
        
            225
                    subject = db.Column(db.Unicode(384)) 
        
            226
                    message = db.Column(db.UnicodeText) 
        
            227
                    repo = db.relationship("Repo", back_populates="posts") 
        
            228
                    owner = db.relationship("User", back_populates="posts") 
        
            229
                    parent = db.relationship("Post", back_populates="children", remote_side="Post.identifier") 
        
            230
                    children = db.relationship("Post", back_populates="parent", remote_side="Post.parentID") 
        
            231
             
        
            232
                    def __init__(self, owner, repo, parent, subject, message): 
        
            233
                        self.identifier = f"{repo.route}/{repo.lastPostID}" 
        
            234
                        self.number = repo.lastPostID 
        
            235
                        self.repoName = repo.route 
        
            236
                        self.repo = repo 
        
            237
                        self.ownerName = owner.username 
        
            238
                        self.owner = owner 
        
            239
                        self.subject = subject 
        
            240
                        self.message = message 
        
            241
                        self.parent = parent 
        
            242
                        repo.lastPostID += 1 
        
            243
             
        
            244
                    def updateDate(self): 
        
            245
                        self.lastUpdated = datetime.now() 
        
            246
                        with db.session.no_autoflush: 
        
            247
                            if self.parent is not None: 
        
            248
                                self.parent.updateDate() 
        
            249
             
        
            250
             
        
            251
            def getPermissionLevel(loggedIn, username, repository): 
        
            252
                user = User.query.filter_by(username=loggedIn).first() 
        
            253
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            254
             
        
            255
                if user and repo: 
        
            256
                    permission = RepoAccess.query.filter_by(user=user, repo=repo).first() 
        
            257
                    if permission: 
        
            258
                        return permission.accessLevel 
        
            259
             
        
            260
                return None 
        
            261
             
        
            262
             
        
            263
            def getVisibility(username, repository): 
        
            264
                repo = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            265
             
        
            266
                if repo: 
        
            267
                    return repo.visibility 
        
            268
             
        
            269
                return None 
        
            270
             
        
            271
             
        
            272
            import gitHTTP 
        
            273
            import jinjaUtils 
        
            274
             
        
            275
             
        
            276
            def humanSize(value, decimals=2, scale=1024, 
        
            277
                          units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")): 
        
            278
                for unit in units: 
        
            279
                    if value < scale: 
        
            280
                        break 
        
            281
                    value /= scale 
        
            282
                if int(value) == value: 
        
            283
                    # do not return decimals, if the value is already round 
        
            284
                    return int(value), unit 
        
            285
                return round(value * 10 ** decimals) / 10 ** decimals, unit 
        
            286
             
        
            287
             
        
            288
            def guessMIME(path): 
        
            289
                if os.path.isdir(path): 
        
            290
                    mimetype = "inode/directory" 
        
            291
                elif magic.from_file(path, mime=True): 
        
            292
                    mimetype = magic.from_file(path, mime=True) 
        
            293
                else: 
        
            294
                    mimetype = "application/octet-stream" 
        
            295
                return mimetype 
        
            296
             
        
            297
             
        
            298
            def convertToHTML(path): 
        
            299
                with open(path, "r") as f: 
        
            300
                    contents = f.read() 
        
            301
                return contents 
        
            302
             
        
            303
             
        
            304
            @app.context_processor 
        
            305
            def default(): 
        
            306
                username = flask.session.get("username") 
        
            307
             
        
            308
                return {"loggedInUser": username} 
        
            309
             
        
            310
             
        
            311
            @app.route("/") 
        
            312
            def main(): 
        
            313
                return flask.render_template("home.html") 
        
            314
             
        
            315
             
        
            316
            @app.route("/about/") 
        
            317
            def about(): 
        
            318
                return flask.render_template("about.html", platform=platform) 
        
            319
             
        
            320
             
        
            321
            @app.route("/settings/", methods=["GET", "POST"]) 
        
            322
            def settings(): 
        
            323
                if flask.request.method == "GET": 
        
            324
                    if not flask.session.get("username"): 
        
            325
                        flask.abort(401) 
        
            326
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            327
             
        
            328
                    return flask.render_template("user-settings.html", user=user) 
        
            329
                else: 
        
            330
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            331
             
        
            332
                    user.displayName = flask.request.form["displayname"] 
        
            333
                    user.URL = flask.request.form["url"] 
        
            334
                    user.company = flask.request.form["company"] 
        
            335
                    user.companyURL = flask.request.form["companyurl"] 
        
            336
                    user.location = flask.request.form["location"] 
        
            337
                    user.showMail = flask.request.form.get("showmail", user.showMail) 
        
            338
             
        
            339
                    db.session.commit() 
        
            340
             
        
            341
                    flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success") 
        
            342
                    return flask.redirect(f"/{flask.session.get('username')}", code=303) 
        
            343
             
        
            344
             
        
            345
            @app.route("/accounts/", methods=["GET", "POST"]) 
        
            346
            def login(): 
        
            347
                if flask.request.method == "GET": 
        
            348
                    return flask.render_template("login.html") 
        
            349
                else: 
        
            350
                    if "login" in flask.request.form: 
        
            351
                        username = flask.request.form["username"] 
        
            352
                        password = flask.request.form["password"] 
        
            353
             
        
            354
                        user = User.query.filter_by(username=username).first() 
        
            355
             
        
            356
                        if user and bcrypt.check_password_hash(user.passwordHashed, password): 
        
            357
                            flask.session["username"] = user.username 
        
            358
                            flask.flash( 
        
            359
                                Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"), 
        
            360
                                category="success") 
        
            361
                            return flask.redirect("/", code=303) 
        
            362
                        elif not user: 
        
            363
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"), 
        
            364
                                        category="alert") 
        
            365
                            return flask.render_template("login.html") 
        
            366
                        else: 
        
            367
                            flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"), 
        
            368
                                        category="error") 
        
            369
                            return flask.render_template("login.html") 
        
            370
                    if "signup" in flask.request.form: 
        
            371
                        username = flask.request.form["username"] 
        
            372
                        password = flask.request.form["password"] 
        
            373
                        password2 = flask.request.form["password2"] 
        
            374
                        email = flask.request.form.get("email") 
        
            375
                        email2 = flask.request.form.get("email2")  # repeat email is a honeypot 
        
            376
                        name = flask.request.form.get("name") 
        
            377
             
        
            378
                        if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            379
                            flask.flash(Markup( 
        
            380
                                "<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            381
                                        category="error") 
        
            382
                            return flask.render_template("login.html") 
        
            383
             
        
            384
                        if username in config.RESERVED_NAMES: 
        
            385
                            flask.flash( 
        
            386
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"), 
        
            387
                                category="error") 
        
            388
                            return flask.render_template("login.html") 
        
            389
             
        
            390
                        userCheck = User.query.filter_by(username=username).first() 
        
            391
                        if userCheck: 
        
            392
                            flask.flash( 
        
            393
                                Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"), 
        
            394
                                category="error") 
        
            395
                            return flask.render_template("login.html") 
        
            396
             
        
            397
                        if password2 != password: 
        
            398
                            flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"), 
        
            399
                                        category="error") 
        
            400
                            return flask.render_template("login.html") 
        
            401
             
        
            402
                        user = User(username, password, email, name) 
        
            403
                        db.session.add(user) 
        
            404
                        db.session.commit() 
        
            405
                        flask.session["username"] = user.username 
        
            406
                        flask.flash(Markup( 
        
            407
                            f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"), 
        
            408
                                    category="success") 
        
            409
                        return flask.redirect("/", code=303) 
        
            410
             
        
            411
             
        
            412
            @app.route("/newrepo/", methods=["GET", "POST"]) 
        
            413
            def newRepo(): 
        
            414
                if flask.request.method == "GET": 
        
            415
                    return flask.render_template("new-repo.html") 
        
            416
                else: 
        
            417
                    name = flask.request.form["name"] 
        
            418
                    visibility = int(flask.request.form["visibility"]) 
        
            419
             
        
            420
                    if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"): 
        
            421
                        flask.flash(Markup( 
        
            422
                                "<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"), 
        
            423
                                category="error") 
        
            424
                        return flask.render_template("new-repo.html") 
        
            425
             
        
            426
                    user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            427
             
        
            428
                    repo = Repo(user, name, visibility) 
        
            429
                    db.session.add(repo) 
        
            430
                    db.session.commit() 
        
            431
             
        
            432
                    if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)): 
        
            433
                        subprocess.run(["git", "init", repo.name], 
        
            434
                                       cwd=os.path.join(config.REPOS_PATH, flask.session.get("username"))) 
        
            435
             
        
            436
                    flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"), 
        
            437
                                category="success") 
        
            438
                    return flask.redirect(repo.route, code=303) 
        
            439
             
        
            440
             
        
            441
            @app.route("/logout") 
        
            442
            def logout(): 
        
            443
                flask.session.clear() 
        
            444
                flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info") 
        
            445
                return flask.redirect("/", code=303) 
        
            446
             
        
            447
             
        
            448
            @app.route("/<username>/") 
        
            449
            def userProfile(username): 
        
            450
                user = User.query.filter_by(username=username).first() 
        
            451
                repos = Repo.query.filter_by(ownerName=username, visibility=2) 
        
            452
                return flask.render_template("user-profile.html", user=user, repos=repos) 
        
            453
             
        
            454
             
        
            455
            @app.route("/<username>/<repository>/") 
        
            456
            def repositoryIndex(username, repository): 
        
            457
                return flask.redirect("./tree", code=302) 
        
            458
             
        
            459
             
        
            460
            @app.route("/<username>/<repository>/raw/<branch>/<path:subpath>") 
        
            461
            def repositoryRaw(username, repository, branch, subpath): 
        
            462
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("user"), username, 
        
            463
                                                                                  repository) is not None): 
        
            464
                    flask.abort(403) 
        
            465
             
        
            466
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            467
             
        
            468
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            469
             
        
            470
                if not os.path.exists(serverRepoLocation): 
        
            471
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            472
                    return flask.render_template("not-found.html"), 404 
        
            473
             
        
            474
                repo = git.Repo(serverRepoLocation) 
        
            475
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            476
                if not repoData.defaultBranch: 
        
            477
                    if repo.heads: 
        
            478
                        repoData.defaultBranch = repo.heads[0].name 
        
            479
                    else: 
        
            480
                        return flask.render_template("empty.html", 
        
            481
                                                     remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            482
                if not branch: 
        
            483
                    branch = repoData.defaultBranch 
        
            484
                    return flask.redirect(f"./{branch}", code=302) 
        
            485
             
        
            486
                if branch.startswith("tag:"): 
        
            487
                    ref = f"tags/{branch[4:]}" 
        
            488
                elif branch.startswith("~"): 
        
            489
                    ref = branch[1:] 
        
            490
                else: 
        
            491
                    ref = f"heads/{branch}" 
        
            492
             
        
            493
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            494
             
        
            495
                try: 
        
            496
                    repo.git.checkout("-f", ref) 
        
            497
                except git.exc.GitCommandError: 
        
            498
                    return flask.render_template("not-found.html"), 404 
        
            499
             
        
            500
                return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath)) 
        
            501
             
        
            502
             
        
            503
            @app.route("/info/<username>/avatar") 
        
            504
            def userAvatar(username): 
        
            505
                serverUserdataLocation = os.path.join(config.USERDATA_PATH, username) 
        
            506
             
        
            507
                if not os.path.exists(serverUserdataLocation): 
        
            508
                    return flask.render_template("not-found.html"), 404 
        
            509
             
        
            510
                return flask.send_from_directory(serverUserdataLocation, "avatar.png") 
        
            511
             
        
            512
             
        
            513
            @app.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""}) 
        
            514
            @app.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""}) 
        
            515
            @app.route("/<username>/<repository>/tree/<branch>/<path:subpath>") 
        
            516
            def repositoryTree(username, repository, branch, subpath): 
        
            517
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            518
                                                                                  repository) is not None): 
        
            519
                    flask.abort(403) 
        
            520
             
        
            521
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            522
             
        
            523
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            524
             
        
            525
                if not os.path.exists(serverRepoLocation): 
        
            526
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            527
                    return flask.render_template("not-found.html"), 404 
        
            528
             
        
            529
                repo = git.Repo(serverRepoLocation) 
        
            530
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            531
                if not repoData.defaultBranch: 
        
            532
                    if repo.heads: 
        
            533
                        repoData.defaultBranch = repo.heads[0].name 
        
            534
                    else: 
        
            535
                        return flask.render_template("empty.html", 
        
            536
                                                     remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200 
        
            537
                if not branch: 
        
            538
                    branch = repoData.defaultBranch 
        
            539
                    return flask.redirect(f"./{branch}", code=302) 
        
            540
             
        
            541
                if branch.startswith("tag:"): 
        
            542
                    ref = f"tags/{branch[4:]}" 
        
            543
                elif branch.startswith("~"): 
        
            544
                    ref = branch[1:] 
        
            545
                else: 
        
            546
                    ref = f"heads/{branch}" 
        
            547
             
        
            548
                ref = ref.replace("~", "/")  # encode slashes for URL support 
        
            549
             
        
            550
                try: 
        
            551
                    repo.git.checkout("-f", ref) 
        
            552
                except git.exc.GitCommandError: 
        
            553
                    return flask.render_template("not-found.html"), 404 
        
            554
             
        
            555
                branches = repo.heads 
        
            556
             
        
            557
                allRefs = [] 
        
            558
                for ref in repo.heads: 
        
            559
                    allRefs.append((ref, "head")) 
        
            560
                for ref in repo.tags: 
        
            561
                    allRefs.append((ref, "tag")) 
        
            562
             
        
            563
                if os.path.isdir(os.path.join(serverRepoLocation, subpath)): 
        
            564
                    files = [] 
        
            565
                    blobs = [] 
        
            566
             
        
            567
                    for entry in os.listdir(os.path.join(serverRepoLocation, subpath)): 
        
            568
                        if not os.path.basename(entry) == ".git": 
        
            569
                            files.append(os.path.join(subpath, entry)) 
        
            570
             
        
            571
                    infos = [] 
        
            572
             
        
            573
                    for file in files: 
        
            574
                        path = os.path.join(serverRepoLocation, file) 
        
            575
                        mimetype = guessMIME(path) 
        
            576
             
        
            577
                        text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode() 
        
            578
             
        
            579
                        sha = text.split("\n")[0] 
        
            580
                        identifier = f"/{username}/{repository}/{sha}" 
        
            581
                        lastCommit = Commit.query.filter_by(identifier=identifier).first() 
        
            582
             
        
            583
                        info = { 
        
            584
                            "name": os.path.basename(file), 
        
            585
                            "serverPath": path, 
        
            586
                            "relativePath": file, 
        
            587
                            "link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file), 
        
            588
                            "size": humanSize(os.path.getsize(path)), 
        
            589
                            "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}", 
        
            590
                            "commit": lastCommit, 
        
            591
                            "shaSize": 7, 
        
            592
                        } 
        
            593
             
        
            594
                        specialIcon = config.matchIcon(os.path.basename(file)) 
        
            595
                        if specialIcon: 
        
            596
                            info["icon"] = specialIcon 
        
            597
                        elif os.path.isdir(path): 
        
            598
                            info["icon"] = config.folderIcon 
        
            599
                        elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            600
                            info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            601
                        else: 
        
            602
                            info["icon"] = config.unknownIcon 
        
            603
             
        
            604
                        if os.path.isdir(path): 
        
            605
                            infos.insert(0, info) 
        
            606
                        else: 
        
            607
                            infos.append(info) 
        
            608
             
        
            609
                    return flask.render_template( 
        
            610
                            "repo-tree.html", 
        
            611
                            username=username, 
        
            612
                            repository=repository, 
        
            613
                            files=infos, 
        
            614
                            subpath=os.path.join("/", subpath), 
        
            615
                            branches=allRefs, 
        
            616
                            current=branch 
        
            617
                    ) 
        
            618
                else: 
        
            619
                    path = os.path.join(serverRepoLocation, subpath) 
        
            620
             
        
            621
                    if not os.path.exists(path): 
        
            622
                        return flask.render_template("not-found.html"), 404 
        
            623
             
        
            624
                    mimetype = guessMIME(path) 
        
            625
                    mode = mimetype.split("/", 1)[0] 
        
            626
                    size = humanSize(os.path.getsize(path)) 
        
            627
             
        
            628
                    specialIcon = config.matchIcon(os.path.basename(path)) 
        
            629
                    if specialIcon: 
        
            630
                        icon = specialIcon 
        
            631
                    elif os.path.isdir(path): 
        
            632
                        icon = config.folderIcon 
        
            633
                    elif mimetypes.guess_type(path)[0] in config.fileIcons: 
        
            634
                        icon = config.fileIcons[mimetypes.guess_type(path)[0]] 
        
            635
                    else: 
        
            636
                        icon = config.unknownIcon 
        
            637
             
        
            638
                    contents = None 
        
            639
                    if mode == "text": 
        
            640
                        contents = convertToHTML(path) 
        
            641
             
        
            642
                    return flask.render_template( 
        
            643
                            "repo-file.html", 
        
            644
                            username=username, 
        
            645
                            repository=repository, 
        
            646
                            file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath), 
        
            647
                            branches=allRefs, 
        
            648
                            current=branch, 
        
            649
                            mode=mode, 
        
            650
                            mimetype=mimetype, 
        
            651
                            detailedtype=magic.from_file(path), 
        
            652
                            size=size, 
        
            653
                            icon=icon, 
        
            654
                            subpath=os.path.join("/", subpath), 
        
            655
                            basename=os.path.basename(path), 
        
            656
                            contents=contents 
        
            657
                    ) 
        
            658
             
        
            659
             
        
            660
            @app.route("/<username>/<repository>/forum/") 
        
            661
            def repositoryForum(username, repository): 
        
            662
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            663
                                                                                  repository) is not None): 
        
            664
                    flask.abort(403) 
        
            665
             
        
            666
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            667
             
        
            668
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            669
             
        
            670
                if not os.path.exists(serverRepoLocation): 
        
            671
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            672
                    return flask.render_template("not-found.html"), 404 
        
            673
             
        
            674
                repo = git.Repo(serverRepoLocation) 
        
            675
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            676
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            677
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            678
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            679
             
        
            680
                return flask.render_template("repo-forum.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post) 
        
            681
             
        
            682
             
        
            683
            @app.route("/<username>/<repository>/forum/new", methods=["POST"]) 
        
            684
            def repositoryForumAdd(username, repository): 
        
            685
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            686
                                                                                  repository) is not None): 
        
            687
                    flask.abort(403) 
        
            688
             
        
            689
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            690
             
        
            691
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            692
             
        
            693
                if not os.path.exists(serverRepoLocation): 
        
            694
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            695
                    return flask.render_template("not-found.html"), 404 
        
            696
             
        
            697
                repo = git.Repo(serverRepoLocation) 
        
            698
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            699
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            700
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            701
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            702
             
        
            703
                post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"]) 
        
            704
             
        
            705
                db.session.add(post) 
        
            706
                db.session.commit() 
        
            707
             
        
            708
                return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=post.number), code=303) 
        
            709
             
        
            710
             
        
            711
            @app.route("/<username>/<repository>/forum/<int:postID>") 
        
            712
            def repositoryForumThread(username, repository, postID): 
        
            713
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            714
                                                                                  repository) is not None): 
        
            715
                    flask.abort(403) 
        
            716
             
        
            717
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            718
             
        
            719
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            720
             
        
            721
                if not os.path.exists(serverRepoLocation): 
        
            722
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            723
                    return flask.render_template("not-found.html"), 404 
        
            724
             
        
            725
                repo = git.Repo(serverRepoLocation) 
        
            726
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            727
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            728
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            729
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            730
             
        
            731
                return flask.render_template("repo-forum-thread.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship, Post=Post, postID=postID, maxPostNesting=4) 
        
            732
             
        
            733
             
        
            734
            @app.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"]) 
        
            735
            def repositoryForumReply(username, repository, postID): 
        
            736
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            737
                                                                                  repository) is not None): 
        
            738
                    flask.abort(403) 
        
            739
             
        
            740
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            741
             
        
            742
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            743
             
        
            744
                if not os.path.exists(serverRepoLocation): 
        
            745
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            746
                    return flask.render_template("not-found.html"), 404 
        
            747
             
        
            748
                repo = git.Repo(serverRepoLocation) 
        
            749
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            750
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            751
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            752
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            753
                if not user: 
        
            754
                    flask.abort(401) 
        
            755
             
        
            756
                parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            757
                post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"]) 
        
            758
             
        
            759
                db.session.add(post) 
        
            760
                post.updateDate() 
        
            761
                db.session.commit() 
        
            762
             
        
            763
                return flask.redirect(flask.url_for("repositoryForumThread", username=username, repository=repository, postID=postID), code=303) 
        
            764
             
        
            765
             
        
            766
            @app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1}) 
        
            767
            @app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1}) 
        
            768
            @app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0}) 
        
            769
            def repositoryForumVote(username, repository, postID, score): 
        
            770
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            771
                                                                                  repository) is not None): 
        
            772
                    flask.abort(403) 
        
            773
             
        
            774
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            775
             
        
            776
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            777
             
        
            778
                if not os.path.exists(serverRepoLocation): 
        
            779
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            780
                    return flask.render_template("not-found.html"), 404 
        
            781
             
        
            782
                repo = git.Repo(serverRepoLocation) 
        
            783
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            784
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            785
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            786
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            787
                if not user: 
        
            788
                    flask.abort(401) 
        
            789
             
        
            790
                post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first() 
        
            791
             
        
            792
                if score: 
        
            793
                    oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            794
                    if oldRelationship: 
        
            795
                        if score == oldRelationship.voteScore: 
        
            796
                            db.session.delete(oldRelationship) 
        
            797
                            post.voteSum -= oldRelationship.voteScore 
        
            798
                        else: 
        
            799
                            post.voteSum -= oldRelationship.voteScore 
        
            800
                            post.voteSum += score 
        
            801
                            oldRelationship.voteScore = score 
        
            802
                    else: 
        
            803
                        relationship = PostVote(user, post, score) 
        
            804
                        post.voteSum += score 
        
            805
                        db.session.add(relationship) 
        
            806
             
        
            807
                    db.session.commit() 
        
            808
             
        
            809
                userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first() 
        
            810
                response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0)) 
        
            811
                response.content_type = "text/plain" 
        
            812
             
        
            813
                return response 
        
            814
             
        
            815
             
        
            816
            @app.route("/<username>/<repository>/users/", methods=["GET", "POST"]) 
        
            817
            def repositoryUsers(username, repository): 
        
            818
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            819
                                                                                  repository) is not None): 
        
            820
                    flask.abort(403) 
        
            821
             
        
            822
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            823
             
        
            824
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            825
             
        
            826
                if not os.path.exists(serverRepoLocation): 
        
            827
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            828
                    return flask.render_template("not-found.html"), 404 
        
            829
             
        
            830
                repo = git.Repo(serverRepoLocation) 
        
            831
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            832
                user = User.query.filter_by(username=flask.session.get("username")).first() 
        
            833
                relationships = RepoAccess.query.filter_by(repo=repoData) 
        
            834
                userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first() 
        
            835
             
        
            836
                if flask.request.method == "GET": 
        
            837
                    return flask.render_template("repo-users.html", username=username, repository=repository, repoData=repoData, relationships=relationships, repo=repo, userRelationship=userRelationship) 
        
            838
                else: 
        
            839
                    if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            840
                        flask.abort(401) 
        
            841
             
        
            842
                    if flask.request.form.get("new-username"): 
        
            843
                        # Create new relationship 
        
            844
                        newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first() 
        
            845
                        relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level")) 
        
            846
                        db.session.add(relationship) 
        
            847
                        db.session.commit() 
        
            848
                    if flask.request.form.get("update-username"): 
        
            849
                        # Create new relationship 
        
            850
                        updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first() 
        
            851
                        relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first() 
        
            852
                        if flask.request.form.get("update-level") == -1: 
        
            853
                            relationship.delete() 
        
            854
                        else: 
        
            855
                            relationship.accessLevel = flask.request.form.get("update-level") 
        
            856
                        db.session.commit() 
        
            857
             
        
            858
                    return flask.redirect(app.url_for("repositoryUsers", username=username, repository=repository)) 
        
            859
             
        
            860
             
        
            861
            @app.route("/<username>/<repository>/branches/") 
        
            862
            def repositoryBranches(username, repository): 
        
            863
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            864
                                                                                  repository) is not None): 
        
            865
                    flask.abort(403) 
        
            866
             
        
            867
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            868
             
        
            869
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            870
             
        
            871
                if not os.path.exists(serverRepoLocation): 
        
            872
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            873
                    return flask.render_template("not-found.html"), 404 
        
            874
             
        
            875
                repo = git.Repo(serverRepoLocation) 
        
            876
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            877
             
        
            878
                return flask.render_template("repo-branches.html", username=username, repository=repository, repoData=repoData, repo=repo) 
        
            879
             
        
            880
             
        
            881
            @app.route("/<username>/<repository>/log/") 
        
            882
            def repositoryLog(username, repository): 
        
            883
                if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username, 
        
            884
                                                                                  repository) is not None): 
        
            885
                    flask.abort(403) 
        
            886
             
        
            887
                serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository) 
        
            888
             
        
            889
                app.logger.info(f"Loading {serverRepoLocation}") 
        
            890
             
        
            891
                if not os.path.exists(serverRepoLocation): 
        
            892
                    app.logger.error(f"Cannot load {serverRepoLocation}") 
        
            893
                    return flask.render_template("not-found.html"), 404 
        
            894
             
        
            895
                repo = git.Repo(serverRepoLocation) 
        
            896
                repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first() 
        
            897
                commits = Commit.query.filter_by(repo=repoData) 
        
            898
             
        
            899
                return flask.render_template("repo-log.html", username=username, repository=repository, repoData=repoData, repo=repo, commits=commits) 
        
            900
             
        
            901
             
        
            902
            @app.route("/<username>/<repository>/settings/") 
        
            903
            def repositorySettings(username, repository): 
        
            904
                if getPermissionLevel(flask.session.get("username"), username, repository) != 2: 
        
            905
                    flask.abort(401) 
        
            906
             
        
            907
                return flask.render_template("repo-settings.html", username=username, repository=repository) 
        
            908
             
        
            909
             
        
            910
            @app.errorhandler(404) 
        
            911
            def e404(error): 
        
            912
                return flask.render_template("not-found.html"), 404 
        
            913
             
        
            914
             
        
            915
            @app.errorhandler(401) 
        
            916
            def e401(error): 
        
            917
                return flask.render_template("unauthorised.html"), 401 
        
            918
             
        
            919
             
        
            920
            @app.errorhandler(403) 
        
            921
            def e403(error): 
        
            922
                return flask.render_template("forbidden.html"), 403 
        
            923
             
        
            924
             
        
            925
            @app.errorhandler(418) 
        
            926
            def e418(error): 
        
            927
                return flask.render_template("teapot.html"), 418 
        
            928
             
        
            929
             
        
            930
            @app.errorhandler(405) 
        
            931
            def e405(error): 
        
            932
                return flask.render_template("method-not-allowed.html"), 405 
        
            933
             
        
            934
             
        
            935
            if __name__ == "__main__": 
        
            936
                app.run(debug=True, port=8080, host="0.0.0.0") 
        
            937