File size: 2,459 Bytes
5db9737 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | import re
from email_validator import validate_email, EmailNotValidError
from ..models import SyntaxResult
# Extended RFC 5321 regex for edge cases
RFC_5321_REGEX = re.compile(
r'^(?:[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+'
r'(?:\.[a-zA-Z0-9!#$%&\'*+/=?^_`{|}~-]+)*'
r'|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]'
r'|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")'
r'@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)'
r'+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?'
r'|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?'
r'|[a-zA-Z0-9-]*[a-zA-Z0-9]:'
r'(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]'
r'|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$'
)
def validate_syntax(email: str) -> SyntaxResult:
"""
Layer 1: Multi-method syntax validation.
Uses email-validator library (RFC 5321/5322) + regex fallback.
"""
# Quick length checks
if not email or len(email) > 254:
return SyntaxResult(
passed=False,
error=f"Email length invalid: {len(email)} chars (max 254)"
)
if '@' not in email:
return SyntaxResult(passed=False, error="Missing @ symbol")
local_part, _, domain_part = email.rpartition('@')
if len(local_part) > 64:
return SyntaxResult(
passed=False,
error="Local part exceeds 64 characters (RFC 5321)"
)
# Try email-validator (most comprehensive)
try:
validated = validate_email(
email,
check_deliverability=False,
allow_smtputf8=True,
allow_empty_local=False
)
return SyntaxResult(
passed=True,
normalized_email=validated.normalized,
local_part=validated.local_part,
domain=validated.domain
)
except EmailNotValidError as e:
# Fallback: check with regex
if RFC_5321_REGEX.match(email):
return SyntaxResult(
passed=True,
normalized_email=email,
local_part=local_part,
domain=domain_part
)
return SyntaxResult(
passed=False,
error=str(e)
)
except Exception as e:
return SyntaxResult(
passed=False,
error=f"Unexpected syntax error: {str(e)}"
)
|