CAT-SOOP is a flexible, programmable learning management system based on the Python programming language. https://catsoop.mit.edu
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

230 lines
7.1 KiB

  1. # This file is part of CAT-SOOP
  2. # Copyright (c) 2011-2020 by The CAT-SOOP Developers <catsoop-dev@mit.edu>
  3. #
  4. # This program is free software: you can redistribute it and/or modify it under
  5. # the terms of the GNU Affero General Public License as published by the Free
  6. # Software Foundation, either version 3 of the License, or (at your option) any
  7. # later version.
  8. #
  9. # This program is distributed in the hope that it will be useful, but WITHOUT
  10. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  11. # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  12. # details.
  13. #
  14. # You should have received a copy of the GNU Affero General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. """
  17. Utilities for generating error messages and displaying error pages
  18. """
  19. import os
  20. import ast
  21. import sys
  22. import traceback
  23. from . import loader
  24. from . import dispatch
  25. from . import base_context
  26. def html_format(string):
  27. """
  28. Returns an HTML-escaped version of the input string, suitable for
  29. insertion into a &lt;pre&gt; tag
  30. **Parameters:**
  31. * `string`: the string to be escaped
  32. **Returns:** an HTML-escaped version of the input string
  33. """
  34. for x, y in (
  35. ("&", "&amp;"),
  36. ("<", "&lt;"),
  37. (">", "&gt;"),
  38. ("\t", " "),
  39. (" ", "&nbsp;"),
  40. ):
  41. string = string.replace(x, y)
  42. return string
  43. def clear_info(context, text):
  44. """
  45. Clear sensitive information from a string
  46. In particular, this function clears the `cs_fs_root` and `cs_data_root`
  47. from tracebacks, as well as any information specified by the
  48. `cs_extra_clear` variable. It also removes the current user's session ID
  49. and API token if they are present.
  50. **Parameters:**
  51. * `context`: the context associated with this request
  52. * `text`: the text (typically a traceback) from which the information
  53. should be removed.
  54. **Returns:** the input string, but with sensitive data replaced.
  55. """
  56. text = text.replace(
  57. context.get("cs_fs_root", base_context.cs_fs_root), "<CATSOOP ROOT>"
  58. )
  59. text = text.replace(
  60. context.get("cs_data_root", base_context.cs_data_root), "<DATA ROOT>"
  61. )
  62. if "cs_sid" in context:
  63. text = text.replace(context["cs_sid"], "<SESSION ID>")
  64. apitok = context.get("user_info", {}).get("api_token", None)
  65. if apitok is not None:
  66. text = text.replace(apitok, "<API TOKEN>")
  67. for i, j in context.get("cs_extra_clear", []):
  68. text = text.replace(i, j)
  69. return text
  70. def error_message_content(context, html=True):
  71. """
  72. Returns a string containing an appropriate error message.
  73. This function bases its output on the last exception, but it also tries
  74. to take into account changes that the CAT-SOOP system makes to some input
  75. files.
  76. **Parameters:**
  77. * `context`: the context associated with this request
  78. **Optional Parameters:**
  79. * `html` (default `True`): whether the output should be HTML-escaped
  80. **Returns:** an error message appropriate for the last exception
  81. """
  82. data_cache = os.path.join(
  83. context.get("cs_data_root", base_context.cs_data_root), "_cached"
  84. )
  85. e = sys.exc_info()
  86. tb_entries = traceback.extract_tb(e[2])
  87. new_entries = []
  88. for tb in tb_entries:
  89. fname, lineno, func, text = tb
  90. lo_fname = fname + ".line_offset"
  91. line_offset = 0
  92. if os.path.isfile(lo_fname):
  93. with open(lo_fname) as offsetfile:
  94. line_offset = ast.literal_eval(offsetfile.read())
  95. lineno -= line_offset
  96. if fname.startswith(data_cache):
  97. fname = fname[len(data_cache) :]
  98. new_entries.append((fname, lineno, func, text))
  99. tb_text = "".join(
  100. traceback.format_list(new_entries) + traceback.format_exception_only(e[0], e[1])
  101. )
  102. i = clear_info(context, tb_text)
  103. if html:
  104. return html_format(i)
  105. return i
  106. def do_error_message(context, msg=None):
  107. """
  108. Display an error page (500 Internal Server Error), including a sensible
  109. error message.
  110. **Parameters:**
  111. * `context`: the context associated with this request
  112. **Optional Parameters:**
  113. * `msg` (default `None`): a custom message to be displayed. If no message
  114. is specified, the output from `catsoop.errors.error_message_content` is
  115. used (which tries to provide a meaningful error message based on the
  116. last exception.
  117. **Returns:** a 3-tuple, as expected by `catsoop.wsgi.application`
  118. """
  119. plain = "data" in context.get("cs_form", {})
  120. new = dict(context)
  121. loader.load_global_data(new)
  122. new["cs_home_link"] = "BASE"
  123. if "cs_user_info" not in new:
  124. new["cs_user_info"] = {}
  125. new["cs_username"] = None
  126. if "cs_handler" in new:
  127. del new["cs_handler"]
  128. m = msg if msg is not None else error_message_content(context, html=(not plain))
  129. if plain:
  130. return (
  131. (500, "Internal Server Error"),
  132. {"Content-type": "text/plain", "Content-length": len(m.encode())},
  133. m,
  134. )
  135. new["cs_original_path"] = ""
  136. new["cs_content"] = ("<pre>ERROR:\n" "%s</pre>") % (m)
  137. e = ': <font color="red">ERROR</font>'
  138. new["cs_header"] = new.get("cs_header", "") + e
  139. new["cs_content_header"] = "An Error Occurred:"
  140. new["cs_top_menu_html"] = ""
  141. new["cs_breadcrumbs_html"] = ""
  142. new["cs_base_font_color"] = "#fff"
  143. s, h, o = dispatch.display_page(new)
  144. o = o.replace(new["cs_base_logo_text"], error_500_logo)
  145. return ("500", "Internal Server Error"), h, o
  146. def do_404_message(context):
  147. """
  148. Display an error page (404 File Not Found), including a sensible error
  149. message.
  150. **Parameters:**
  151. * `context`: the context associated with this request
  152. **Returns:** a 3-tuple, as expected by `catsoop.wsgi.application`
  153. """
  154. new = dict(context)
  155. loader.load_global_data(new)
  156. new["cs_home_link"] = "BASE"
  157. if "cs_user_info" not in new:
  158. new["cs_user_info"] = {}
  159. new["cs_username"] = None
  160. if "cs_handler" in new:
  161. del new["cs_handler"]
  162. new["cs_content"] = (
  163. "<pre>CAT-SOOP could not find the specified file or resource:\n" "%r</pre>"
  164. ) % (new["cs_original_path"])
  165. new["cs_original_path"] = ""
  166. e = ': <font color="red">404</font>'
  167. new["cs_header"] = new.get("cs_header", "") + e
  168. new["cs_content_header"] = "File/Resource Not Found"
  169. new["cs_top_menu_html"] = ""
  170. new["cs_breadcrumbs_html"] = ""
  171. new["cs_base_font_color"] = "#fff"
  172. s, h, o = dispatch.display_page(new)
  173. o = o.replace(new["cs_base_logo_text"], error_404_logo)
  174. return ("404", "File Not Found"), h, o
  175. error_404_logo = (
  176. "\ ???????? "
  177. "\n/ /\__/\ "
  178. "\n\__=( @_@ )="
  179. "\n(__________) "
  180. "\n |_ |_ |_ |_ "
  181. )
  182. """This alternate logo replaces the CAT-SOOP logo on when a 404 (File Not
  183. Found) error is encountered."""
  184. error_500_logo = (
  185. " _ _ _ _ "
  186. "\n _|__|__|__| "
  187. "\n ( _ ___)"
  188. "\n=( x X )= \\"
  189. "\n \/ \/ /"
  190. "\n \\"
  191. )
  192. """This alternate logo replaces the CAT-SOOP logo on when a 500 (Internal
  193. Server Error) error is encountered."""