Skip to content

Instantly share code, notes, and snippets.

@Jared314
Last active December 26, 2020 04:25
Show Gist options
  • Save Jared314/6479982 to your computer and use it in GitHub Desktop.
Save Jared314/6479982 to your computer and use it in GitHub Desktop.
Adventures in Validating Email Addresses with Clojure
admin@mailserver1
[email protected]
[email protected]
[email protected]
[email protected]
postbox@com
user@[192.168.2.1]
user@[IPv6:2001:db8:1ff::a0b:dbd0]
"much.more unusual"@example.com
" "@example.org
"[email protected]"@example.com
"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
!#$%&'*+-/=?^_`{}|[email protected]
"()<>[]:,;@\\\"!#$%&'*+-/=?^_`{}| ~.a"@example.org
(comment)user1.(comment)@(comment)example.com(comment)
;; Version 1
(ns validate-email
(:require [clojure.test :refer :all]))
(defn email-valid? [s] (not (nil? (re-matches #"[a-z\.\+]+@[a-z\.]+" s))))
(deftest filters-invalid-email-addresses
(let [invalid-emails ["Abc.example.com"
"A@b@[email protected]"
"a\"b(c)d,e:f;g<h>i[j\\k][email protected]"
"just\"not\"[email protected]"
"this is\"not\\[email protected]"
"this\\ still\\\"not\\\\[email protected]"
"not(this)[email protected]"]]
(is (every? (complement email-valid?) invalid-emails))))
;; Version 2
(ns validate-email
(:require [clojure.test :refer :all]
[instaparse.core :as insta]))
(def parse-email-address
(insta/parser "ADDRESS = LOCAL <'@'> DOMAIN
LOCAL = COMMENT? LOCALVALUE COMMENT?
LOCALVALUE = (LOCALSANDQUOTES DOT)* LOCALSANDQUOTES
<LOCALSANDQUOTES> = (LOCALS | QUOTEDSTRING)
<LOCALS> = (LETTER | NUMBER | LOCALSYMBOL)+
QUOTEDSTRING = <'\"'> (LETTER | NUMBER | LOCALSYMBOL | DOT | QUOTEDSYMBOL)* <'\"'>
<LOCALSYMBOL> = '!'|'#'|'$'|'%'|'&'|'*'|'+'|'-'|'/'|'?'|'^'|'_'|'`'|'{'|'}'|'~'|'='|'|'|\"'\"
<QUOTEDSYMBOL> = ' '|'('|')'|','|':'|';'|'<'|'>'|'@'|'['|']'|QUOTEDESCAPEDSYMBOL
QUOTEDESCAPEDSYMBOL = <'\\\\'> ('\\\\' | '\"' | LETTER | NUMBER | HEXENTITY | ' ' | '\0')
DOMAIN = COMMENT? DOMAINVALUE COMMENT?
DOMAINVALUE = (IPADDRESS | HOSTNAME)
IPADDRESS = <'['> (IP4ADDRESS|IP6ADDRESS|DOMAINLITERAL) <']'>
IP4ADDRESS = NUMBER NUMBER? NUMBER? '.' NUMBER NUMBER? NUMBER? '.' NUMBER NUMBER? NUMBER? '.' NUMBER NUMBER? NUMBER?
IP6ADDRESS = <'IPv6:'> (HEX | ':')+
DOMAINLITERAL = DOMAINLITERALSYMBOL+
DOMAINLITERALSYMBOL = LETTER|NUMBER|'!'|'\"'|'#'|'$'|'%'|'&'|\"'\"|'('|')'|'*'|'+'|','|'-'|'.'|'/'|':'|';'|'<'|'='|'>'|'?'|'@'|'^'|'_'|'`'|'{'|'|'|'}'|'~'
HOSTNAME = (LABEL DOT)* LABEL
LABEL = &(LETTER | NUMBER) (LETTER | NUMBER | '-')* (LETTER | NUMBER)
<DOT> = '.'
COMMENT = <'('> (LETTER | COMMENTESCAPEDSYMBOL | HEXENTITY | COMMENT)* <')'>
<COMMENTESCAPEDSYMBOL> = '\\\\' ('\\\\' | '\"' | ')')
HEXENTITY = <'&'> <'#'> <'x'> HEX+ <';'>
<HEX> = NUMBER|'a'|'b'|'c'|'d'|'e'|'f'|'A'|'B'|'C'|'D'|'E'|'F'
<NUMBER> = '0'|'1'|'2'|'3'|'4'|'5'|'6'|'7'|'8'|'9'
<LETTER> = 'a'|'b'|'c'|'d'|'e'|'f'|'g'|'h'|'i'|'j'|'k'|'l'|'m'|'n'|'o'|'p'|'q'|'r'|'s'|'t'|'u'|'v'|'w'|'x'|'y'|'z'|
'A'|'B'|'C'|'D'|'E'|'F'|'G'|'H'|'I'|'J'|'K'|'L'|'M'|'N'|'O'|'P'|'Q'|'R'|'S'|'T'|'U'|'V'|'W'|'X'|'Y'|'Z'
"))
(defn email-valid? [e]
(not (insta/failure? (parse-email-address e))))
(deftest filters-invalid-email-addresses
(let [invalid-emails ["Abc.example.com"
"A@b@[email protected]"
"a\"b(c)d,e:f;g<h>i[j\\k][email protected]"
"just\"not\"[email protected]"
"this is\"not\\[email protected]"
"this\\ still\\\"not\\\\[email protected]"
"not(this)[email protected]"]]
(is (every? (complement email-valid?) invalid-emails))))
(deftest validates-valid-email-addresses
(let [valid-emails ["admin@mailserver1"
"[email protected]"
"[email protected]"
"[email protected]"
"postbox@com"
"user@[192.168.2.1]"
"user@[IPv6:2001:db8:1ff::a0b:dbd0]"
"\"much.more unusual\"@example.com"
"\"[email protected]\"@example.com"
"\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@strange.example.com"
"!#$%&'*+-/=?^_`{}|[email protected]"
"\"()<>[]:,;@\\\\\\\"!#$%&'*+-/=?^_`{}| ~.a\"@example.org"
"\" \"@example.org"
"(comment)john.smith(comment)@(comment)example.com(comment)"]]
(is (every? email-valid? valid-emails))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment