File size: 5,457 Bytes
2ec2072
 
 
800e08b
2ab666f
2ec2072
800e08b
2ec2072
2ab666f
800e08b
2ab666f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec2072
 
 
 
2ab666f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2ec2072
800e08b
2ec2072
 
 
800e08b
2ec2072
2ab666f
 
 
 
 
 
 
 
 
800e08b
 
 
 
2ec2072
2ab666f
800e08b
 
 
 
 
 
 
2ab666f
800e08b
 
 
 
2ec2072
800e08b
2ec2072
 
 
 
800e08b
 
 
 
2ec2072
 
800e08b
 
2ab666f
800e08b
2ab666f
 
800e08b
2ec2072
2ab666f
 
800e08b
2ab666f
 
 
 
800e08b
2ab666f
2ec2072
 
 
 
 
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import gradio as gr
import re
import dns.resolver
from typing import Tuple
from dns.exception import DNSException

def check_syntax(mail_address: str) -> Tuple[bool, str]:
    """이메일 μ£Όμ†Œ ꡬ문 검사"""
    # RFC 5322 ν‘œμ€€μ„ 기반으둜 ν•œ 더 μ—„κ²©ν•œ 이메일 μ •κ·œμ‹ νŒ¨ν„΄
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    
    # κΈ°λ³Έ νŒ¨ν„΄ 검사
    if not re.match(pattern, mail_address):
        return False, "❌ 이메일 μ£Όμ†Œ ν˜•μ‹μ΄ μ˜¬λ°”λ₯΄μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."
    
    # μΆ”κ°€ μœ νš¨μ„± 검사
    local_part, domain = mail_address.split('@')
    
    # 둜컬 파트 검사
    if len(local_part) > 64:
        return False, "❌ 이메일 μ£Όμ†Œμ˜ @ μ•žλΆ€λΆ„μ΄ λ„ˆλ¬΄ κΉλ‹ˆλ‹€."
    if local_part.startswith('.') or local_part.endswith('.'):
        return False, "❌ 이메일 μ£Όμ†Œμ˜ @ μ•žλΆ€λΆ„μ΄ 점(.)으둜 μ‹œμž‘ν•˜κ±°λ‚˜ 끝날 수 μ—†μŠ΅λ‹ˆλ‹€."
    if '..' in local_part:
        return False, "❌ 이메일 μ£Όμ†Œμ˜ @ μ•žλΆ€λΆ„μ— μ—°μ†λœ 점(..)이 μžˆμ„ 수 μ—†μŠ΅λ‹ˆλ‹€."
        
    # 도메인 파트 검사
    if len(domain) > 255:
        return False, "❌ 도메인이 λ„ˆλ¬΄ κΉλ‹ˆλ‹€."
    if domain.startswith('-') or domain.endswith('-'):
        return False, "❌ 도메인은 ν•˜μ΄ν”ˆ(-)으둜 μ‹œμž‘ν•˜κ±°λ‚˜ 끝날 수 μ—†μŠ΅λ‹ˆλ‹€."
    if not all(part.isalnum() or '-' in part for part in domain.split('.')):
        return False, "❌ 도메인에 ν—ˆμš©λ˜μ§€ μ•ŠλŠ” λ¬Έμžκ°€ ν¬ν•¨λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€."
        
    return True, "βœ… 이메일 μ£Όμ†Œ ν˜•μ‹μ΄ μ˜¬λ°”λ¦…λ‹ˆλ‹€."

def is_disposable_domain(domain: str) -> bool:
    """μž„μ‹œ 이메일 도메인 체크"""
    disposable_domains = {
        'tempmail.com', 'throwawaymail.com', 'mailinator.com', 
        'temp-mail.org', 'fake-email.com', 'temporary-mail.net'
    }
    return domain.lower() in disposable_domains

def check_dns(domain: str) -> Tuple[bool, str]:
    """DNS MX λ ˆμ½”λ“œ 검사"""
    try:
        # μž„μ‹œ 이메일 도메인 체크
        if is_disposable_domain(domain):
            return False, "❌ μž„μ‹œ 이메일 도메인은 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€."
            
        # DNS MX λ ˆμ½”λ“œ 확인
        records = dns.resolver.resolve(domain, 'MX')
        if not records:
            return False, "❌ λ„λ©”μΈμ˜ 메일 μ„œλ²„λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."
            
        # μ‹€μ œ MX λ ˆμ½”λ“œ λ‚΄μš© 확인
        mx_records = [str(r.exchange).rstrip('.') for r in records]
        if not mx_records:
            return False, "❌ μœ νš¨ν•œ 메일 μ„œλ²„ 정보가 μ—†μŠ΅λ‹ˆλ‹€."
            
        # A λ ˆμ½”λ“œλ„ 확인
        try:
            dns.resolver.resolve(domain, 'A')
        except DNSException:
            try:
                dns.resolver.resolve(domain, 'AAAA')
            except DNSException:
                return False, "❌ 도메인이 μ‹€μ œλ‘œ μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€."
                
        return True, "βœ… λ„λ©”μΈμ˜ 메일 μ„œλ²„κ°€ μ‘΄μž¬ν•©λ‹ˆλ‹€."
        
    except dns.resolver.NXDOMAIN:
        return False, "❌ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λ„λ©”μΈμž…λ‹ˆλ‹€."
    except dns.resolver.NoAnswer:
        return False, "❌ 도메인에 메일 μ„œλ²„ 정보가 μ—†μŠ΅λ‹ˆλ‹€."
    except dns.resolver.NoNameservers:
        return False, "❌ DNS μ„œλ²„μ— μ—°κ²°ν•  수 μ—†μŠ΅λ‹ˆλ‹€."
    except Exception as e:
        return False, f"❌ DNS 확인 쀑 였λ₯˜ λ°œμƒ: {str(e)}"

def validate_email(mail_address: str) -> str:
    """이메일 μ£Όμ†Œ μ’…ν•© 검증"""
    results = []
    
    # 1. κΈ°λ³Έ μž…λ ₯κ°’ 검사
    if not mail_address or not isinstance(mail_address, str):
        return "❌ μœ νš¨ν•˜μ§€ μ•Šμ€ μž…λ ₯μž…λ‹ˆλ‹€."
    
    mail_address = mail_address.strip()
    if not mail_address:
        return "❌ 이메일 μ£Όμ†Œλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”."
    
    # 2. ꡬ문 검사
    syntax_valid, syntax_msg = check_syntax(mail_address)
    results.append(syntax_msg)
    if not syntax_valid:
        return "\n".join(results)
    
    # 3. 도메인 μΆ”μΆœ 및 DNS 검사
    try:
        domain = mail_address.split('@')[1]
        dns_valid, dns_msg = check_dns(domain)
        results.append(dns_msg)
        if not dns_valid:
            return "\n".join(results)
        
        # λͺ¨λ“  검증 톡과
        results.append("βœ… 이메일 μ£Όμ†Œκ°€ μœ νš¨ν•©λ‹ˆλ‹€.")
        
    except Exception as e:
        results.append(f"❌ 검증 쀑 였λ₯˜ λ°œμƒ: {str(e)}")
    
    return "\n".join(results)

# Gradio μΈν„°νŽ˜μ΄μŠ€ ꡬ성
iface = gr.Interface(
    fn=validate_email,
    inputs=gr.Textbox(
        label="이메일 μ£Όμ†Œλ₯Ό μž…λ ₯ν•˜μ„Έμš”",
        placeholder="[email protected]"
    ),
    outputs=gr.Textbox(label="검증 κ²°κ³Ό"),
    title="이메일 μ£Όμ†Œ 검증 도ꡬ",
    description="""
    이 λ„κ΅¬λŠ” λ‹€μŒ 사항듀을 κ²€μ¦ν•©λ‹ˆλ‹€:
    1. 이메일 μ£Όμ†Œ ν˜•μ‹ 검사 (RFC 5322 ν‘œμ€€ μ€€μˆ˜)
    2. λ„λ©”μΈμ˜ 메일 μ„œλ²„(MX λ ˆμ½”λ“œ) 쑴재 μ—¬λΆ€ 확인
    3. λ„λ©”μΈμ˜ μ‹€μ œ 쑴재 μ—¬λΆ€ 확인
    4. μž„μ‹œ 이메일 도메인 필터링
    """,
    examples=[
        ["[email protected]"],
        ["[email protected]"],
        ["[email protected]"],
        ["malformed@@email.com"],
        ["[email protected]"],
        ["test@domain"],
        ["[email protected]"]
    ],
    theme=gr.themes.Soft()
)

# μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰
if __name__ == "__main__":
    iface.launch()