utils.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. import hashlib
  2. from datetime import datetime, timedelta
  3. from django.conf import settings
  4. from django.core.exceptions import PermissionDenied
  5. from django.http import Http404
  6. from django.urls import resolve, reverse
  7. from django.utils import html, timezone
  8. from django.utils.encoding import force_str
  9. from django.utils.module_loading import import_string
  10. MISAGO_SLUGIFY = getattr(settings, "MISAGO_SLUGIFY", "misago.core.slugify.default")
  11. slugify = import_string(MISAGO_SLUGIFY)
  12. def format_plaintext_for_html(string):
  13. return html.linebreaks(html.urlize(html.escape(string)))
  14. def encode_json_html(string):
  15. return string.replace("<", r"\u003C")
  16. ISO8601_FORMATS = ("%Y-%m-%dT%H:%M:%S", "%Y-%m-%dT%H:%M:%S.%f")
  17. def parse_iso8601_string(value):
  18. """turns ISO 8601 string into datetime object"""
  19. value = force_str(value, strings_only=True).rstrip("Z")
  20. for format_str in ISO8601_FORMATS:
  21. try:
  22. parsed_value = datetime.strptime(value, format_str)
  23. break
  24. except ValueError:
  25. try:
  26. parsed_value = datetime.strptime(value[:-6], format_str)
  27. break
  28. except ValueError:
  29. pass
  30. else:
  31. raise ValueError("failed to hydrate the %s timestamp" % value)
  32. offset_str = value[-6:]
  33. if offset_str and offset_str[0] in ("-", "+"):
  34. tz_offset = timedelta(hours=int(offset_str[1:3]), minutes=int(offset_str[4:6]))
  35. tz_offset = tz_offset.seconds // 60
  36. if offset_str[0] == "-":
  37. tz_offset *= -1
  38. else:
  39. tz_offset = 0
  40. tz_correction = timezone.get_fixed_timezone(tz_offset)
  41. return timezone.make_aware(parsed_value, tz_correction)
  42. def hide_post_parameters(request):
  43. """
  44. Mark request as having sensitive parameters
  45. We can't use decorator because of DRF uses custom HttpRequest
  46. that is incompatibile with Django's decorator
  47. """
  48. request.sensitive_post_parameters = "__ALL__"
  49. def clean_return_path(request):
  50. """return path utility that returns return path from referer or POST"""
  51. if request.method == "POST" and "return_path" in request.POST:
  52. return _get_return_path_from_post(request)
  53. return _get_return_path_from_referer(request)
  54. def _get_return_path_from_post(request):
  55. return_path = request.POST.get("return_path")
  56. try:
  57. if not return_path:
  58. raise ValueError()
  59. if not return_path.startswith("/"):
  60. raise ValueError()
  61. resolve(return_path)
  62. return return_path
  63. except (Http404, ValueError):
  64. return None
  65. def _get_return_path_from_referer(request):
  66. referer = request.META.get("HTTP_REFERER")
  67. try:
  68. if not referer:
  69. raise ValueError()
  70. if not referer.startswith(request.scheme):
  71. raise ValueError()
  72. referer = referer[len(request.scheme) + 3 :]
  73. if not referer.startswith(request.META["HTTP_HOST"]):
  74. raise ValueError()
  75. referer = referer[len(request.META["HTTP_HOST"].rstrip("/")) :]
  76. if not referer.startswith("/"):
  77. raise ValueError()
  78. resolve(referer)
  79. return referer
  80. except (Http404, KeyError, ValueError):
  81. return None
  82. def is_request_to_misago(request):
  83. try:
  84. return request._request_to_misago
  85. except AttributeError:
  86. request._request_to_misago = _is_request_path_under_misago(request)
  87. return request._request_to_misago
  88. def _is_request_path_under_misago(request):
  89. # We are assuming that forum_index link is root of all Misago links
  90. forum_index = reverse("misago:index")
  91. path = request.path
  92. if len(forum_index) > len(path):
  93. return False
  94. return path[: len(forum_index)] == forum_index
  95. def is_referer_local(request):
  96. referer = request.META.get("HTTP_REFERER")
  97. if not referer:
  98. return False
  99. if not referer.startswith(request.scheme):
  100. return False
  101. referer = referer[len(request.scheme) + 3 :]
  102. if not referer.startswith(request.META["HTTP_HOST"]):
  103. return False
  104. referer = referer[len(request.META["HTTP_HOST"].rstrip("/")) :]
  105. if not referer.startswith("/"):
  106. return False
  107. return True
  108. def get_exception_message(exception=None, default_message=None):
  109. if not exception:
  110. return default_message
  111. try:
  112. return exception.args[0]
  113. except IndexError:
  114. return default_message
  115. def clean_ids_list(ids_list, error_message):
  116. try:
  117. return list(map(int, ids_list))
  118. except (ValueError, TypeError):
  119. raise PermissionDenied(error_message)
  120. def get_host_from_address(address):
  121. if not address:
  122. return None
  123. if address.lower().startswith("https://"):
  124. address = address[8:]
  125. if address.lower().startswith("http://"):
  126. address = address[7:]
  127. address = address.lstrip("/")
  128. if "/" in address:
  129. address = address.split("/")[0] or address
  130. if ":" in address:
  131. address = address.split(":")[0] or address
  132. return address
  133. HASH_LENGTH = 8
  134. def get_file_hash(file_obj):
  135. if not file_obj.size:
  136. return "0" * HASH_LENGTH
  137. file_hash = hashlib.md5()
  138. for chunk in file_obj.chunks():
  139. file_hash.update(chunk)
  140. return file_hash.hexdigest()[:HASH_LENGTH]