File size: 4,821 Bytes
d889b51
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import re
import gradio as gr

# ---------------------------
# 1. REGEX PATTERNS
# ---------------------------
EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b")
URL_RE = re.compile(r"\bhttps?://\S+|\bwww\.\S+\b", re.IGNORECASE)
IP_RE = re.compile(r"\b\d{1,3}(?:\.\d{1,3}){3}\b")
DATE_RE = re.compile(r"\b(?:\d{1,2}[\/\-.]\d{1,2}[\/\-.]\d{2,4}|\d{4}-\d{2}-\d{2})\b")

# téléphones un peu larges, on filtrera les trop courts
PHONE_RE = re.compile(r"(?:\+?\d[\d\s().-]{5,}\d)")

# cartes bancaires : on détecte 13 à 19 chiffres avec séparateurs
CARD_RE = re.compile(r"\b(?:\d[ -]*?){13,19}\b")

# SSN US basique
SSN_RE = re.compile(r"\b\d{3}-\d{2}-\d{4}\b")


# ---------------------------
# 2. UTILITAIRES
# ---------------------------
def luhn_valid(number: str) -> bool:
    """Vérifie un numéro de carte avec Luhn (simple)."""
    digits = [int(d) for d in number if d.isdigit()]
    if len(digits) < 13 or len(digits) > 19:
        return False
    checksum = 0
    parity = len(digits) % 2
    for i, d in enumerate(digits):
        if i % 2 == parity:
            d = d * 2
            if d > 9:
                d -= 9
        checksum += d
    return checksum % 10 == 0


def mask_value(value: str, style: str = "tag") -> str:
    """
    style:
      - tag -> [PII]
      - stars -> ********
      - keep_len -> même longueur mais *
    """
    if style == "tag":
        return "[PII]"
    elif style == "stars":
        return "*" * len(value)
    elif style == "keep_len":
        return "".join("*" if not c.isspace() else c for c in value)
    return "[PII]"


# ---------------------------
# 3. ANONYMISATION
# ---------------------------
def anonymize(text: str, style: str):
    if not text.strip():
        return "Paste or type a text to anonymize.", "{}"

    counts = {
        "email": 0,
        "url": 0,
        "ip": 0,
        "date": 0,
        "phone": 0,
        "card": 0,
        "ssn": 0,
    }

    # 1) Emails
    def repl_email(m):
        counts["email"] += 1
        return mask_value(m.group(), style)
    text = EMAIL_RE.sub(repl_email, text)

    # 2) URLs
    def repl_url(m):
        counts["url"] += 1
        return mask_value(m.group(), style)
    text = URL_RE.sub(repl_url, text)

    # 3) IPs
    def repl_ip(m):
        counts["ip"] += 1
        return mask_value(m.group(), style)
    text = IP_RE.sub(repl_ip, text)

    # 4) Dates
    def repl_date(m):
        counts["date"] += 1
        return mask_value(m.group(), style)
    text = DATE_RE.sub(repl_date, text)

    # 5) SSN
    def repl_ssn(m):
        counts["ssn"] += 1
        return mask_value(m.group(), style)
    text = SSN_RE.sub(repl_ssn, text)

    # 6) Cartes bancaires (avec Luhn)
    def repl_card(m):
        raw = m.group()
        digits = "".join(ch for ch in raw if ch.isdigit())
        if luhn_valid(digits):
            counts["card"] += 1
            return mask_value(raw, style)
        return raw  # pas une vraie carte
    text = CARD_RE.sub(repl_card, text)

    # 7) Téléphones
    def repl_phone(m):
        raw = m.group()
        # on nettoie
        digits = "".join(ch for ch in raw if ch.isdigit())
        if len(digits) < 6:  # trop court pour être un vrai numéro
            return raw
        counts["phone"] += 1
        return mask_value(raw, style)
    text = PHONE_RE.sub(repl_phone, text)

    # petit résumé JSON
    import json
    stats = json.dumps({k: v for k, v in counts.items() if v > 0}, indent=2, ensure_ascii=False)
    return text, stats or "{}"


# ---------------------------
# 4. GRADIO UI
# ---------------------------
with gr.Blocks(title="PII-Shield — Anonymizer") as demo:
    gr.Markdown("# 🛡️ PII-Shield — Text Anonymizer")
    gr.Markdown("Collez un SMS, un e-mail ou un texte administratif. L’outil masque automatiquement emails, téléphones, URLs, IPs, dates, SSN et cartes bancaires (Luhn).")

    with gr.Row():
        inp = gr.Textbox(lines=10, label="Texte à anonymiser")
        style = gr.Radio(
            ["tag", "stars", "keep_len"],
            value="tag",
            label="Style de masquage",
            info="tag = [PII], stars = ********, keep_len = même longueur"
        )

    btn = gr.Button("Analyser / Masquer")

    out = gr.Textbox(lines=10, label="Texte anonymisé")
    stats = gr.JSON(label="Éléments détectés")

    examples = [
        "Salut Marie, écris-moi à marie.dupont@example.com ou appelle au +33 6 12 34 56 78. RDV le 12/05/2024.",
        "Payment card: 4111 1111 1111 1111, IP: 192.168.0.1, site: https://example.org",
        "US client SSN: 123-45-6789"
    ]
    gr.Examples(examples=examples, inputs=inp, outputs=[out, stats])

    btn.click(anonymize, inputs=[inp, style], outputs=[out, stats])

if __name__ == "__main__":
    demo.launch()