stringutils.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. # -*- coding: utf-8 -*-
  2. # Copyright 2014-2016 OpenMarket Ltd
  3. # Copyright 2020 The Matrix.org Foundation C.I.C.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import random
  17. import re
  18. import string
  19. import six
  20. from six import PY2, PY3
  21. from six.moves import range
  22. from synapse.api.errors import Codes, SynapseError
  23. _string_with_symbols = string.digits + string.ascii_letters + ".,;:^&*-_+=#~@"
  24. # https://matrix.org/docs/spec/client_server/r0.6.0#post-matrix-client-r0-register-email-requesttoken
  25. # Note: The : character is allowed here for older clients, but will be removed in a
  26. # future release. Context: https://github.com/matrix-org/synapse/issues/6766
  27. client_secret_regex = re.compile(r"^[0-9a-zA-Z\.\=\_\-\:]+$")
  28. # random_string and random_string_with_symbols are used for a range of things,
  29. # some cryptographically important, some less so. We use SystemRandom to make sure
  30. # we get cryptographically-secure randoms.
  31. rand = random.SystemRandom()
  32. def random_string(length):
  33. return "".join(rand.choice(string.ascii_letters) for _ in range(length))
  34. def random_string_with_symbols(length):
  35. return "".join(rand.choice(_string_with_symbols) for _ in range(length))
  36. def is_ascii(s):
  37. if PY3:
  38. if isinstance(s, bytes):
  39. try:
  40. s.decode("ascii").encode("ascii")
  41. except UnicodeDecodeError:
  42. return False
  43. except UnicodeEncodeError:
  44. return False
  45. return True
  46. try:
  47. s.encode("ascii")
  48. except UnicodeEncodeError:
  49. return False
  50. except UnicodeDecodeError:
  51. return False
  52. else:
  53. return True
  54. def to_ascii(s):
  55. """Converts a string to ascii if it is ascii, otherwise leave it alone.
  56. If given None then will return None.
  57. """
  58. if PY3:
  59. return s
  60. if s is None:
  61. return None
  62. try:
  63. return s.encode("ascii")
  64. except UnicodeEncodeError:
  65. return s
  66. def exception_to_unicode(e):
  67. """Helper function to extract the text of an exception as a unicode string
  68. Args:
  69. e (Exception): exception to be stringified
  70. Returns:
  71. unicode
  72. """
  73. # urgh, this is a mess. The basic problem here is that psycopg2 constructs its
  74. # exceptions with PyErr_SetString, with a (possibly non-ascii) argument. str() will
  75. # then produce the raw byte sequence. Under Python 2, this will then cause another
  76. # error if it gets mixed with a `unicode` object, as per
  77. # https://github.com/matrix-org/synapse/issues/4252
  78. # First of all, if we're under python3, everything is fine because it will sort this
  79. # nonsense out for us.
  80. if not PY2:
  81. return str(e)
  82. # otherwise let's have a stab at decoding the exception message. We'll circumvent
  83. # Exception.__str__(), which would explode if someone raised Exception(u'non-ascii')
  84. # and instead look at what is in the args member.
  85. if len(e.args) == 0:
  86. return ""
  87. elif len(e.args) > 1:
  88. return six.text_type(repr(e.args))
  89. msg = e.args[0]
  90. if isinstance(msg, bytes):
  91. return msg.decode("utf-8", errors="replace")
  92. else:
  93. return msg
  94. def assert_valid_client_secret(client_secret):
  95. """Validate that a given string matches the client_secret regex defined by the spec"""
  96. if client_secret_regex.match(client_secret) is None:
  97. raise SynapseError(
  98. 400, "Invalid client_secret parameter", errcode=Codes.INVALID_PARAM
  99. )