Coverage for /home/runner/work/viur-core/viur-core/viur/src/viur/core/bones/email.py: 97%

42 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-01 22:44 +0000

1import re 

2import string 

3from encodings import idna 

4from viur.core.bones.string import StringBone 

5from viur.core import i18n 

6 

7_DNS_LABEL_RE = re.compile(r"(?!-)[a-z0-9-]{1,63}(?<!-)", re.IGNORECASE) 

8"""A single DNS label per RFC 1035: 1-63 alphanumerics or hyphens, no leading or trailing hyphen.""" 

9 

10 

11class EmailBone(StringBone): 

12 """ 

13 The EmailBone class is a designed to store syntactically validated email addresses. 

14 

15 This class provides an email validation method, ensuring that the given email address conforms to the 

16 required format and structure. 

17 """ 

18 type = "str.email" 

19 """ 

20 A string representing the type of the bone, in this case "str.email". 

21 """ 

22 

23 def isInvalid(self, value): 

24 """ 

25 Checks if the provided email address is valid or not. 

26 

27 :param str value: The email address to be validated. 

28 :returns: An error message if the email address is invalid or None if it is valid. 

29 :rtype: str, None 

30 

31 The method checks if the provided email address is valid according to the following criteria: 

32 

33 1. The email address must not be empty. 

34 2. The email address must be shorter than 256 characters. 

35 3. The local part (account) must be shorter than or equal to 64 characters. 

36 4. The email address must contain an "@" symbol, separating the local part (account) and the domain part. 

37 5. The domain part must be a valid IDNA-encoded string and should not contain any spaces. 

38 6. The local part (account) should only contain valid characters. 

39 7. The local part (account) can also contain Unicode characters within the range of U+0080 to U+10FFFF. 

40 """ 

41 if not value: 

42 return i18n.translate("core.bones.error.novalueentered", "No value entered") 

43 

44 is_valid = True 

45 

46 try: 

47 assert len(value) < 256 

48 account, domain = value.split(u"@") 

49 assert account and domain 

50 assert len(account) <= 64 

51 except (ValueError, AssertionError): 

52 is_valid = False 

53 

54 if is_valid: 

55 validChars = string.ascii_letters + string.digits + "!#$%&'*+-/=?^_`{|}~." 

56 unicodeLowerBound = u"\u0080" 

57 unicodeUpperBound = u"\U0010FFFF" 

58 for char in account: 

59 if not (char in validChars or (char >= unicodeLowerBound and char <= unicodeUpperBound)): 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true

60 is_valid = False 

61 # Validate each domain label (RFC 1035). idna.ToASCII does not reject a 

62 # leading or trailing hyphen (e.g. "-online") or an over-long label, so the 

63 # label structure is checked with _DNS_LABEL_RE on the IDNA-encoded form; 

64 # otherwise addresses like "foo@-online.de" pass. 

65 labels = domain.split(".") 

66 if len(labels) < 2 or labels[-1].isdigit(): 

67 is_valid = False 

68 else: 

69 for label in labels: 

70 try: 

71 asciiLabel = idna.ToASCII(label).decode("ascii") 

72 except Exception: 

73 is_valid = False 

74 break 

75 if not _DNS_LABEL_RE.fullmatch(asciiLabel): 

76 is_valid = False 

77 break 

78 

79 if not is_valid: 

80 return i18n.translate("core.bones.error.invalidemail", "Invalid email entered")