Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

common.py 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import re
  2. from urllib.parse import urlparse
  3. from django.conf import settings
  4. from django.core.exceptions import PermissionDenied
  5. from django.core.mail import mail_managers
  6. from django.http import HttpResponsePermanentRedirect
  7. from django.urls import is_valid_path
  8. from django.utils.deprecation import MiddlewareMixin
  9. from django.utils.http import escape_leading_slashes
  10. class CommonMiddleware(MiddlewareMixin):
  11. """
  12. "Common" middleware for taking care of some basic operations:
  13. - Forbid access to User-Agents in settings.DISALLOWED_USER_AGENTS
  14. - URL rewriting: Based on the APPEND_SLASH and PREPEND_WWW settings,
  15. append missing slashes and/or prepends missing "www."s.
  16. - If APPEND_SLASH is set and the initial URL doesn't end with a
  17. slash, and it is not found in urlpatterns, form a new URL by
  18. appending a slash at the end. If this new URL is found in
  19. urlpatterns, return an HTTP redirect to this new URL; otherwise
  20. process the initial URL as usual.
  21. This behavior can be customized by subclassing CommonMiddleware and
  22. overriding the response_redirect_class attribute.
  23. """
  24. response_redirect_class = HttpResponsePermanentRedirect
  25. def process_request(self, request):
  26. """
  27. Check for denied User-Agents and rewrite the URL based on
  28. settings.APPEND_SLASH and settings.PREPEND_WWW
  29. """
  30. # Check for denied User-Agents
  31. user_agent = request.META.get("HTTP_USER_AGENT")
  32. if user_agent is not None:
  33. for user_agent_regex in settings.DISALLOWED_USER_AGENTS:
  34. if user_agent_regex.search(user_agent):
  35. raise PermissionDenied("Forbidden user agent")
  36. # Check for a redirect based on settings.PREPEND_WWW
  37. host = request.get_host()
  38. must_prepend = settings.PREPEND_WWW and host and not host.startswith("www.")
  39. redirect_url = ("%s://www.%s" % (request.scheme, host)) if must_prepend else ""
  40. # Check if a slash should be appended
  41. if self.should_redirect_with_slash(request):
  42. path = self.get_full_path_with_slash(request)
  43. else:
  44. path = request.get_full_path()
  45. # Return a redirect if necessary
  46. if redirect_url or path != request.get_full_path():
  47. redirect_url += path
  48. return self.response_redirect_class(redirect_url)
  49. def should_redirect_with_slash(self, request):
  50. """
  51. Return True if settings.APPEND_SLASH is True and appending a slash to
  52. the request path turns an invalid path into a valid one.
  53. """
  54. if settings.APPEND_SLASH and not request.path_info.endswith("/"):
  55. urlconf = getattr(request, "urlconf", None)
  56. if not is_valid_path(request.path_info, urlconf):
  57. match = is_valid_path("%s/" % request.path_info, urlconf)
  58. if match:
  59. view = match.func
  60. return getattr(view, "should_append_slash", True)
  61. return False
  62. def get_full_path_with_slash(self, request):
  63. """
  64. Return the full path of the request with a trailing slash appended.
  65. Raise a RuntimeError if settings.DEBUG is True and request.method is
  66. POST, PUT, or PATCH.
  67. """
  68. new_path = request.get_full_path(force_append_slash=True)
  69. # Prevent construction of scheme relative urls.
  70. new_path = escape_leading_slashes(new_path)
  71. if settings.DEBUG and request.method in ("POST", "PUT", "PATCH"):
  72. raise RuntimeError(
  73. "You called this URL via %(method)s, but the URL doesn't end "
  74. "in a slash and you have APPEND_SLASH set. Django can't "
  75. "redirect to the slash URL while maintaining %(method)s data. "
  76. "Change your form to point to %(url)s (note the trailing "
  77. "slash), or set APPEND_SLASH=False in your Django settings."
  78. % {
  79. "method": request.method,
  80. "url": request.get_host() + new_path,
  81. }
  82. )
  83. return new_path
  84. def process_response(self, request, response):
  85. """
  86. When the status code of the response is 404, it may redirect to a path
  87. with an appended slash if should_redirect_with_slash() returns True.
  88. """
  89. # If the given URL is "Not Found", then check if we should redirect to
  90. # a path with a slash appended.
  91. if response.status_code == 404 and self.should_redirect_with_slash(request):
  92. return self.response_redirect_class(self.get_full_path_with_slash(request))
  93. # Add the Content-Length header to non-streaming responses if not
  94. # already set.
  95. if not response.streaming and not response.has_header("Content-Length"):
  96. response.headers["Content-Length"] = str(len(response.content))
  97. return response
  98. class BrokenLinkEmailsMiddleware(MiddlewareMixin):
  99. def process_response(self, request, response):
  100. """Send broken link emails for relevant 404 NOT FOUND responses."""
  101. if response.status_code == 404 and not settings.DEBUG:
  102. domain = request.get_host()
  103. path = request.get_full_path()
  104. referer = request.META.get("HTTP_REFERER", "")
  105. if not self.is_ignorable_request(request, path, domain, referer):
  106. ua = request.META.get("HTTP_USER_AGENT", "<none>")
  107. ip = request.META.get("REMOTE_ADDR", "<none>")
  108. mail_managers(
  109. "Broken %slink on %s"
  110. % (
  111. (
  112. "INTERNAL "
  113. if self.is_internal_request(domain, referer)
  114. else ""
  115. ),
  116. domain,
  117. ),
  118. "Referrer: %s\nRequested URL: %s\nUser agent: %s\n"
  119. "IP address: %s\n" % (referer, path, ua, ip),
  120. fail_silently=True,
  121. )
  122. return response
  123. def is_internal_request(self, domain, referer):
  124. """
  125. Return True if the referring URL is the same domain as the current
  126. request.
  127. """
  128. # Different subdomains are treated as different domains.
  129. return bool(re.match("^https?://%s/" % re.escape(domain), referer))
  130. def is_ignorable_request(self, request, uri, domain, referer):
  131. """
  132. Return True if the given request *shouldn't* notify the site managers
  133. according to project settings or in situations outlined by the inline
  134. comments.
  135. """
  136. # The referer is empty.
  137. if not referer:
  138. return True
  139. # APPEND_SLASH is enabled and the referer is equal to the current URL
  140. # without a trailing slash indicating an internal redirect.
  141. if settings.APPEND_SLASH and uri.endswith("/") and referer == uri[:-1]:
  142. return True
  143. # A '?' in referer is identified as a search engine source.
  144. if not self.is_internal_request(domain, referer) and "?" in referer:
  145. return True
  146. # The referer is equal to the current URL, ignoring the scheme (assumed
  147. # to be a poorly implemented bot).
  148. parsed_referer = urlparse(referer)
  149. if parsed_referer.netloc in ["", domain] and parsed_referer.path == uri:
  150. return True
  151. return any(pattern.search(uri) for pattern in settings.IGNORABLE_404_URLS)