docs_server.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. # -*- coding: utf-8 -*-
  2. """
  3. (c) 2014-2016 - Copyright Red Hat Inc
  4. Authors:
  5. Pierre-Yves Chibon <pingou@pingoured.fr>
  6. """
  7. from __future__ import unicode_literals, absolute_import
  8. import logging
  9. import os
  10. import flask
  11. import pygit2
  12. from binaryornot.helpers import is_binary_string
  13. import pagure.config
  14. import pagure.doc_utils
  15. import pagure.exceptions
  16. import pagure.lib.query
  17. import pagure.lib.mimetype
  18. import pagure.forms
  19. # Create the application.
  20. APP = flask.Flask(__name__)
  21. # set up FAS
  22. APP.config = pagure.config.reload_config()
  23. SESSION = pagure.lib.query.create_session(APP.config["DB_URL"])
  24. if not APP.debug:
  25. APP.logger.addHandler(
  26. pagure.mail_logging.get_mail_handler(
  27. smtp_server=APP.config.get("SMTP_SERVER", "127.0.0.1"),
  28. mail_admin=APP.config.get("MAIL_ADMIN", APP.config["EMAIL_ERROR"]),
  29. from_email=APP.config.get(
  30. "FROM_EMAIL", "pagure@fedoraproject.org"
  31. ),
  32. )
  33. )
  34. # Send classic logs into syslog
  35. SHANDLER = logging.StreamHandler()
  36. SHANDLER.setLevel(APP.config.get("log_level", "INFO"))
  37. APP.logger.addHandler(SHANDLER)
  38. _log = logging.getLogger(__name__)
  39. TMPL_HTML = """
  40. <!DOCTYPE html>
  41. <html lang='en'>
  42. <head>
  43. <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  44. <style type="text/css">
  45. ul {{
  46. margin: 0;
  47. padding: 0;
  48. }}
  49. </style>
  50. </head>
  51. <body>
  52. {content}
  53. </body>
  54. </html>
  55. """
  56. def __get_tree(repo_obj, tree, filepath, index=0, extended=False):
  57. """ Retrieve the entry corresponding to the provided filename in a
  58. given tree.
  59. """
  60. filename = filepath[index]
  61. if isinstance(tree, pygit2.Blob): # pragma: no cover
  62. # If we were given a blob, then let's just return it
  63. return (tree, None, None)
  64. for element in tree:
  65. if element.name == filename or (
  66. not filename and element.name.startswith("index")
  67. ):
  68. # If we have a folder we must go one level deeper
  69. if element.filemode == 16384:
  70. if (index + 1) == len(filepath):
  71. filepath.append("")
  72. return __get_tree(
  73. repo_obj,
  74. repo_obj[element.oid],
  75. filepath,
  76. index=index + 1,
  77. extended=True,
  78. )
  79. else:
  80. return (element, tree, False)
  81. if filename == "":
  82. return (None, tree, extended)
  83. else:
  84. raise pagure.exceptions.FileNotFoundException(
  85. "File %s not found" % ("/".join(filepath),)
  86. )
  87. def __get_tree_and_content(repo_obj, commit, path):
  88. """ Return the tree and the content of the specified file. """
  89. (blob_or_tree, tree_obj, extended) = __get_tree(
  90. repo_obj, commit.tree, path
  91. )
  92. if blob_or_tree is None:
  93. return (tree_obj, None, None)
  94. if not repo_obj[blob_or_tree.oid]:
  95. # Not tested and no idea how to test it, but better safe than sorry
  96. flask.abort(404, "File not found")
  97. if isinstance(blob_or_tree, pygit2.TreeEntry): # Returned a file
  98. filename = blob_or_tree.name
  99. name, ext = os.path.splitext(filename)
  100. blob_obj = repo_obj[blob_or_tree.oid]
  101. if not is_binary_string(blob_obj.data):
  102. try:
  103. content, safe = pagure.doc_utils.convert_readme(
  104. blob_obj.data, ext
  105. )
  106. if safe:
  107. filename = name + ".html"
  108. except pagure.exceptions.PagureEncodingException:
  109. content = blob_obj.data
  110. else:
  111. content = blob_obj.data
  112. tree = sorted(tree_obj, key=lambda x: x.filemode)
  113. return (tree, content, filename)
  114. @APP.route("/<repo>/")
  115. @APP.route("/<namespace>.<repo>/")
  116. @APP.route("/<repo>/<path:filename>")
  117. @APP.route("/<namespace>.<repo>/<path:filename>")
  118. @APP.route("/fork/<username>/<repo>/")
  119. @APP.route("/fork/<namespace>.<username>/<repo>/")
  120. @APP.route("/fork/<username>/<repo>/<path:filename>")
  121. @APP.route("/fork/<namespace>.<username>/<repo>/<path:filename>")
  122. def view_docs(repo, username=None, namespace=None, filename=None):
  123. """ Display the documentation
  124. """
  125. if "." in repo:
  126. namespace, repo = repo.split(".", 1)
  127. repo = pagure.lib.query.get_authorized_project(
  128. SESSION, repo, user=username, namespace=namespace
  129. )
  130. if not repo:
  131. flask.abort(404, "Project not found")
  132. if not repo.settings.get("project_documentation", True):
  133. flask.abort(404, "This project has documentation disabled")
  134. reponame = repo.repopath("docs")
  135. if not os.path.exists(reponame):
  136. flask.abort(404, "Documentation not found")
  137. repo_obj = pygit2.Repository(reponame)
  138. if not repo_obj.is_empty:
  139. commit = repo_obj[repo_obj.head.target]
  140. else:
  141. flask.abort(
  142. 404,
  143. flask.Markup(
  144. "No content found in the repository, you may want to read "
  145. 'the <a href="'
  146. 'https://docs.pagure.org/pagure/usage/using_doc.html">'
  147. "Using the doc repository of your project</a> documentation."
  148. ),
  149. )
  150. content = None
  151. tree = None
  152. if not filename:
  153. path = [""]
  154. else:
  155. path = [it for it in filename.split("/") if it]
  156. if commit:
  157. try:
  158. (tree, content, filename) = __get_tree_and_content(
  159. repo_obj, commit, path
  160. )
  161. except pagure.exceptions.FileNotFoundException as err:
  162. flask.flash("%s" % err, "error")
  163. except Exception as err:
  164. _log.exception(err)
  165. flask.abort(500, "Unkown error encountered and reported")
  166. if not content:
  167. if not tree or not len(tree):
  168. flask.abort(404, "No content found in the repository")
  169. html = "<li>"
  170. for el in tree:
  171. name = el.name
  172. # Append a trailing '/' to the folders
  173. if el.filemode == 16384:
  174. name += "/"
  175. html += '<ul><a href="{0}">{1}</a></ul>'.format(name, name)
  176. html += "</li>"
  177. content = TMPL_HTML.format(content=html)
  178. mimetype = "text/html"
  179. else:
  180. mimetype, _ = pagure.lib.mimetype.guess_type(filename, content)
  181. return flask.Response(content, mimetype=mimetype)