Blob Blame Raw
# -*- coding: utf-8 -*-

"""
 (c) 2014-2016 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

from __future__ import unicode_literals, absolute_import

import logging
import os

import flask
import pygit2

from binaryornot.helpers import is_binary_string

import pagure.config
import pagure.doc_utils
import pagure.exceptions
import pagure.lib.mimetype
import pagure.lib.model_base
import pagure.lib.query
import pagure.forms

# Create the application.
APP = flask.Flask(__name__)

# set up FAS
APP.config = pagure.config.reload_config()

SESSION = pagure.lib.model_base.create_session(APP.config["DB_URL"])

if not APP.debug:
    APP.logger.addHandler(
        pagure.mail_logging.get_mail_handler(
            smtp_server=APP.config.get("SMTP_SERVER", "127.0.0.1"),
            mail_admin=APP.config.get("MAIL_ADMIN", APP.config["EMAIL_ERROR"]),
            from_email=APP.config.get(
                "FROM_EMAIL", "pagure@fedoraproject.org"
            ),
        )
    )

# Send classic logs into syslog
SHANDLER = logging.StreamHandler()
SHANDLER.setLevel(APP.config.get("log_level", "INFO"))
APP.logger.addHandler(SHANDLER)

_log = logging.getLogger(__name__)

TMPL_HTML = """
<!DOCTYPE html>
<html lang='en'>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <style type="text/css">
    ul {{
      margin: 0;
      padding: 0;
    }}
  </style>
</head>
<body>
{content}
</body>
</html>
"""


def __get_tree(repo_obj, tree, filepath, index=0, extended=False):
    """ Retrieve the entry corresponding to the provided filename in a
    given tree.
    """
    filename = filepath[index]
    if isinstance(tree, pygit2.Blob):  # pragma: no cover
        # If we were given a blob, then let's just return it
        return (tree, None, None)

    for element in tree:
        if element.name == filename or (
            not filename and element.name.startswith("index")
        ):
            # If we have a folder we must go one level deeper
            if element.filemode == 16384:
                if (index + 1) == len(filepath):
                    filepath.append("")
                return __get_tree(
                    repo_obj,
                    repo_obj[element.oid],
                    filepath,
                    index=index + 1,
                    extended=True,
                )
            else:
                return (element, tree, False)

    if filename == "":
        return (None, tree, extended)
    else:
        raise pagure.exceptions.FileNotFoundException(
            "File %s not found" % ("/".join(filepath),)
        )


def __get_tree_and_content(repo_obj, commit, path):
    """ Return the tree and the content of the specified file. """

    (blob_or_tree, tree_obj, extended) = __get_tree(
        repo_obj, commit.tree, path
    )

    if blob_or_tree is None:
        return (tree_obj, None, None)

    if not repo_obj[blob_or_tree.oid]:
        # Not tested and no idea how to test it, but better safe than sorry
        flask.abort(404, description="File not found")

    if isinstance(blob_or_tree, pygit2.TreeEntry):  # Returned a file
        filename = blob_or_tree.name
        name, ext = os.path.splitext(filename)
        blob_obj = repo_obj[blob_or_tree.oid]
        if not is_binary_string(blob_obj.data):
            try:
                content, safe = pagure.doc_utils.convert_readme(
                    blob_obj.data, ext
                )
                if safe:
                    filename = name + ".html"
            except pagure.exceptions.PagureEncodingException:
                content = blob_obj.data
        else:
            content = blob_obj.data

    tree = sorted(tree_obj, key=lambda x: x.filemode)
    return (tree, content, filename)


@APP.route("/<repo>/")
@APP.route("/<namespace>.<repo>/")
@APP.route("/<repo>/<path:filename>")
@APP.route("/<namespace>.<repo>/<path:filename>")
@APP.route("/fork/<username>/<repo>/")
@APP.route("/fork/<namespace>.<username>/<repo>/")
@APP.route("/fork/<username>/<repo>/<path:filename>")
@APP.route("/fork/<namespace>.<username>/<repo>/<path:filename>")
def view_docs(repo, username=None, namespace=None, filename=None):
    """ Display the documentation
    """
    if "." in repo:
        namespace, repo = repo.split(".", 1)

    repo = pagure.lib.query.get_authorized_project(
        SESSION, repo, user=username, namespace=namespace
    )

    if not repo:
        flask.abort(404, description="Project not found")

    if not repo.settings.get("project_documentation", True):
        flask.abort(404, description="This project has documentation disabled")

    reponame = repo.repopath("docs")
    if not os.path.exists(reponame):
        flask.abort(404, description="Documentation not found")

    repo_obj = pygit2.Repository(reponame)

    if not repo_obj.is_empty:
        commit = repo_obj[repo_obj.head.target]
    else:
        flask.abort(
            404,
            flask.Markup(
                "No content found in the repository, you may want to read "
                'the <a href="'
                'https://docs.pagure.org/pagure/usage/using_doc.html">'
                "Using the doc repository of your project</a> documentation."
            ),
        )

    content = None
    tree = None
    if not filename:
        path = [""]
    else:
        path = [it for it in filename.split("/") if it]

    if commit:
        try:
            (tree, content, filename) = __get_tree_and_content(
                repo_obj, commit, path
            )
        except pagure.exceptions.FileNotFoundException as err:
            flask.flash("%s" % err, "error")
        except Exception as err:
            _log.exception(err)
            flask.abort(
                500, description="Unkown error encountered and reported"
            )

    if not content:
        if not tree or not len(tree):
            flask.abort(404, description="No content found in the repository")
        html = "<li>"
        for el in tree:
            name = el.name
            # Append a trailing '/' to the folders
            if el.filemode == 16384:
                name += "/"
            html += '<ul><a href="{0}">{1}</a></ul>'.format(name, name)
        html += "</li>"
        content = TMPL_HTML.format(content=html)
        mimetype = "text/html"
    else:
        mimetype, _ = pagure.lib.mimetype.guess_type(filename, content)

    return flask.Response(content, mimetype=mimetype)