email-validate 0.3.2 → 1.0.0
raw patch · 62 files changed
+441/−823 lines, 62 filesdep +HUnitdep +QuickCheckdep +attoparsecdep −parsecdep −rangesdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: HUnit, QuickCheck, attoparsec, bytestring, email-validate, test-framework, test-framework-hunit, test-framework-quickcheck2
Dependencies removed: parsec, ranges
Dependency ranges changed: base
API changes (from Hackage documentation)
Files
- .git/COMMIT_EDITMSG +0/−3
- .git/FETCH_HEAD +0/−1
- .git/HEAD +0/−1
- .git/ORIG_HEAD +0/−1
- .git/config +0/−14
- .git/description +0/−1
- .git/hooks/applypatch-msg.sample +0/−15
- .git/hooks/commit-msg.sample +0/−24
- .git/hooks/post-update.sample +0/−8
- .git/hooks/pre-applypatch.sample +0/−14
- .git/hooks/pre-commit.sample +0/−50
- .git/hooks/pre-rebase.sample +0/−169
- .git/hooks/prepare-commit-msg.sample +0/−36
- .git/hooks/update.sample +0/−128
- .git/index binary
- .git/info/exclude +0/−6
- .git/logs/HEAD +0/−4
- .git/logs/refs/heads/master +0/−4
- .git/logs/refs/remotes/origin/HEAD +0/−1
- .git/logs/refs/remotes/origin/master +0/−1
- .git/objects/15/b5b61cf94c1a3f745d249cb27c277506e6b163 binary
- .git/objects/21/2b997b8e6ea14f49212506a6f24add7af4a497 binary
- .git/objects/23/53b6eaeee1c824371c12bad78d4053ec52041d binary
- .git/objects/24/d18fba10dee5c4ce4aa2af9d83aec2514b3fe8 binary
- .git/objects/2d/9f07a20bc1f037e1729cef6419adb04e31cb6a +0/−2
- .git/objects/30/795b93e4270a5a2a3df24d1e41b7e10a24614a binary
- .git/objects/3c/218af146e571eb4c82060eb761dcdb78d51ce6 binary
- .git/objects/3c/eee01bed32bce1a61c10fd5f2e1446fa00ce0e binary
- .git/objects/3f/7084c8c996d45e41dd9ddcb0d1e03c6d4693f0 +0/−1
- .git/objects/59/a24bf0dd1195866e5f971ac6d70abdb34a29d3 binary
- .git/objects/5b/de0de962274e26328ecffabb4e9f6430a10cc9 binary
- .git/objects/60/436f01329fb1e98b20c972b8f2dff34a605e4f binary
- .git/objects/6b/7e3d59554eef8f09a0fe8699732541b92536dd binary
- .git/objects/6e/b270b673a8ae8db8e5e2a5e765065933943523 +0/−2
- .git/objects/70/b31b49e6f1ff5ad7b25c262ecbdc1d6fc0e1c4 binary
- .git/objects/79/14ad75ef8004bc68244408f494abb69913f35c binary
- .git/objects/79/4e34c8dcac0093b4b669bf4847ce4fd4b1fd4c +0/−1
- .git/objects/7a/b9d07f7dc5409bfbd9d154fd1e16ec97360c39 binary
- .git/objects/7c/cafa1430896619ca670cc5849ba034b871972b binary
- .git/objects/83/a5e0534a073a57d8bf04c7957cb88be1a3f675 binary
- .git/objects/8c/e32485dfe28fdbee865fe42cb2e9992110d022 binary
- .git/objects/8d/d5f4f7a7503628bf518fb265434b1017385554 binary
- .git/objects/a3/62c7ee447b16dedc823854165c0178c1d37d12 binary
- .git/objects/ab/98b6067c8dea629a6d423255d8f6eca4d9addd binary
- .git/objects/ca/abd581f6cf7157ce211a91112cf5047e527be7 binary
- .git/objects/ce/21b8e64947926b006ca76fe444f5536c355703 binary
- .git/objects/d5/3be38fca3a8e4e1a736800e156f0e8be0c0d74 binary
- .git/objects/dc/d45a039547b065e629b98f3a069216070a4032 binary
- .git/objects/e2/b34424ef1cefd7197309a466e1c98bc940215b binary
- .git/objects/e6/ab6abbd47b7761ca51305e98621f6ed101fd4d binary
- .git/objects/e8/90c931c46c7b2a4f53db4edcdfbff25943bfb9 binary
- .git/objects/e8/9c8a1a38fe8ad397e3dcc9b0eeb30bbfb78433 binary
- .git/objects/f4/d0360b79923043eee16f821b2073b866c56fec binary
- .git/packed-refs +0/−2
- .git/refs/heads/master +0/−1
- .git/refs/remotes/origin/HEAD +0/−1
- .git/refs/remotes/origin/master +0/−1
- Text/Email/Validate.hs +0/−324
- email-validate.cabal +21/−7
- src/Text/Email/Parser.hs +100/−0
- src/Text/Email/Validate.hs +39/−0
- tests/Main.hs +281/−0
@@ -1,3 +0,0 @@-Revert "revert removal of eof marker"--This reverts commit caabd581f6cf7157ce211a91112cf5047e527be7.
@@ -1,1 +0,0 @@-2353b6eaeee1c824371c12bad78d4053ec52041d branch 'master' of https://github.com/Porges/email-validate-hs
@@ -1,1 +0,0 @@-ref: refs/heads/master
@@ -1,1 +0,0 @@-6eb270b673a8ae8db8e5e2a5e765065933943523
@@ -1,14 +0,0 @@-[core]- repositoryformatversion = 0- filemode = false- bare = false- logallrefupdates = true- symlinks = false- ignorecase = true- hideDotFiles = dotGitOnly-[remote "origin"]- fetch = +refs/heads/*:refs/remotes/origin/*- url = https://github.com/Porges/email-validate-hs.git-[branch "master"]- remote = origin- merge = refs/heads/master
@@ -1,1 +0,0 @@-Unnamed repository; edit this file 'description' to name the repository.
@@ -1,15 +0,0 @@-#!/bin/sh-#-# An example hook script to check the commit log message taken by-# applypatch from an e-mail message.-#-# The hook should exit with non-zero status after issuing an-# appropriate message if it wants to stop the commit. The hook is-# allowed to edit the commit message file.-#-# To enable this hook, rename this file to "applypatch-msg".--. git-sh-setup-test -x "$GIT_DIR/hooks/commit-msg" &&- exec "$GIT_DIR/hooks/commit-msg" ${1+"$@"}-:
@@ -1,24 +0,0 @@-#!/bin/sh-#-# An example hook script to check the commit log message.-# Called by "git commit" with one argument, the name of the file-# that has the commit message. The hook should exit with non-zero-# status after issuing an appropriate message if it wants to stop the-# commit. The hook is allowed to edit the commit message file.-#-# To enable this hook, rename this file to "commit-msg".--# Uncomment the below to add a Signed-off-by line to the message.-# Doing this in a hook is a bad idea in general, but the prepare-commit-msg-# hook is more suited to it.-#-# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')-# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"--# This example catches duplicate Signed-off-by lines.--test "" = "$(grep '^Signed-off-by: ' "$1" |- sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {- echo >&2 Duplicate Signed-off-by lines.- exit 1-}
@@ -1,8 +0,0 @@-#!/bin/sh-#-# An example hook script to prepare a packed repository for use over-# dumb transports.-#-# To enable this hook, rename this file to "post-update".--exec git update-server-info
@@ -1,14 +0,0 @@-#!/bin/sh-#-# An example hook script to verify what is about to be committed-# by applypatch from an e-mail message.-#-# The hook should exit with non-zero status after issuing an-# appropriate message if it wants to stop the commit.-#-# To enable this hook, rename this file to "pre-applypatch".--. git-sh-setup-test -x "$GIT_DIR/hooks/pre-commit" &&- exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}-:
@@ -1,50 +0,0 @@-#!/bin/sh-#-# An example hook script to verify what is about to be committed.-# Called by "git commit" with no arguments. The hook should-# exit with non-zero status after issuing an appropriate message if-# it wants to stop the commit.-#-# To enable this hook, rename this file to "pre-commit".--if git rev-parse --verify HEAD >/dev/null 2>&1-then- against=HEAD-else- # Initial commit: diff against an empty tree object- against=4b825dc642cb6eb9a060e54bf8d69288fbee4904-fi--# If you want to allow non-ascii filenames set this variable to true.-allownonascii=$(git config hooks.allownonascii)--# Redirect output to stderr.-exec 1>&2--# Cross platform projects tend to avoid non-ascii filenames; prevent-# them from being added to the repository. We exploit the fact that the-# printable range starts at the space character and ends with tilde.-if [ "$allownonascii" != "true" ] &&- # Note that the use of brackets around a tr range is ok here, (it's- # even required, for portability to Solaris 10's /usr/bin/tr), since- # the square bracket bytes happen to fall in the designated range.- test $(git diff --cached --name-only --diff-filter=A -z $against |- LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0-then- echo "Error: Attempt to add a non-ascii file name."- echo- echo "This can cause problems if you want to work"- echo "with people on other platforms."- echo- echo "To be portable it is advisable to rename the file ..."- echo- echo "If you know what you are doing you can disable this"- echo "check using:"- echo- echo " git config hooks.allownonascii true"- echo- exit 1-fi--# If there are whitespace errors, print the offending file names and fail.-exec git diff-index --check --cached $against --
@@ -1,169 +0,0 @@-#!/bin/sh-#-# Copyright (c) 2006, 2008 Junio C Hamano-#-# The "pre-rebase" hook is run just before "git rebase" starts doing-# its job, and can prevent the command from running by exiting with-# non-zero status.-#-# The hook is called with the following parameters:-#-# $1 -- the upstream the series was forked from.-# $2 -- the branch being rebased (or empty when rebasing the current branch).-#-# This sample shows how to prevent topic branches that are already-# merged to 'next' branch from getting rebased, because allowing it-# would result in rebasing already published history.--publish=next-basebranch="$1"-if test "$#" = 2-then- topic="refs/heads/$2"-else- topic=`git symbolic-ref HEAD` ||- exit 0 ;# we do not interrupt rebasing detached HEAD-fi--case "$topic" in-refs/heads/??/*)- ;;-*)- exit 0 ;# we do not interrupt others.- ;;-esac--# Now we are dealing with a topic branch being rebased-# on top of master. Is it OK to rebase it?--# Does the topic really exist?-git show-ref -q "$topic" || {- echo >&2 "No such branch $topic"- exit 1-}--# Is topic fully merged to master?-not_in_master=`git rev-list --pretty=oneline ^master "$topic"`-if test -z "$not_in_master"-then- echo >&2 "$topic is fully merged to master; better remove it."- exit 1 ;# we could allow it, but there is no point.-fi--# Is topic ever merged to next? If so you should not be rebasing it.-only_next_1=`git rev-list ^master "^$topic" ${publish} | sort`-only_next_2=`git rev-list ^master ${publish} | sort`-if test "$only_next_1" = "$only_next_2"-then- not_in_topic=`git rev-list "^$topic" master`- if test -z "$not_in_topic"- then- echo >&2 "$topic is already up-to-date with master"- exit 1 ;# we could allow it, but there is no point.- else- exit 0- fi-else- not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"`- /usr/bin/perl -e '- my $topic = $ARGV[0];- my $msg = "* $topic has commits already merged to public branch:\n";- my (%not_in_next) = map {- /^([0-9a-f]+) /;- ($1 => 1);- } split(/\n/, $ARGV[1]);- for my $elem (map {- /^([0-9a-f]+) (.*)$/;- [$1 => $2];- } split(/\n/, $ARGV[2])) {- if (!exists $not_in_next{$elem->[0]}) {- if ($msg) {- print STDERR $msg;- undef $msg;- }- print STDERR " $elem->[1]\n";- }- }- ' "$topic" "$not_in_next" "$not_in_master"- exit 1-fi--exit 0--################################################################--This sample hook safeguards topic branches that have been-published from being rewound.--The workflow assumed here is:-- * Once a topic branch forks from "master", "master" is never- merged into it again (either directly or indirectly).-- * Once a topic branch is fully cooked and merged into "master",- it is deleted. If you need to build on top of it to correct- earlier mistakes, a new topic branch is created by forking at- the tip of the "master". This is not strictly necessary, but- it makes it easier to keep your history simple.-- * Whenever you need to test or publish your changes to topic- branches, merge them into "next" branch.--The script, being an example, hardcodes the publish branch name-to be "next", but it is trivial to make it configurable via-$GIT_DIR/config mechanism.--With this workflow, you would want to know:--(1) ... if a topic branch has ever been merged to "next". Young- topic branches can have stupid mistakes you would rather- clean up before publishing, and things that have not been- merged into other branches can be easily rebased without- affecting other people. But once it is published, you would- not want to rewind it.--(2) ... if a topic branch has been fully merged to "master".- Then you can delete it. More importantly, you should not- build on top of it -- other people may already want to- change things related to the topic as patches against your- "master", so if you need further changes, it is better to- fork the topic (perhaps with the same name) afresh from the- tip of "master".--Let's look at this example:-- o---o---o---o---o---o---o---o---o---o "next"- / / / /- / a---a---b A / /- / / / /- / / c---c---c---c B /- / / / \ /- / / / b---b C \ /- / / / / \ /- ---o---o---o---o---o---o---o---o---o---o---o "master"---A, B and C are topic branches.-- * A has one fix since it was merged up to "next".-- * B has finished. It has been fully merged up to "master" and "next",- and is ready to be deleted.-- * C has not merged to "next" at all.--We would want to allow C to be rebased, refuse A, and encourage-B to be deleted.--To compute (1):-- git rev-list ^master ^topic next- git rev-list ^master next-- if these match, topic has not merged in next at all.--To compute (2):-- git rev-list master..topic-- if this is empty, it is fully merged to "master".
@@ -1,36 +0,0 @@-#!/bin/sh-#-# An example hook script to prepare the commit log message.-# Called by "git commit" with the name of the file that has the-# commit message, followed by the description of the commit-# message's source. The hook's purpose is to edit the commit-# message file. If the hook fails with a non-zero status,-# the commit is aborted.-#-# To enable this hook, rename this file to "prepare-commit-msg".--# This hook includes three examples. The first comments out the-# "Conflicts:" part of a merge commit.-#-# The second includes the output of "git diff --name-status -r"-# into the message, just before the "git status" output. It is-# commented because it doesn't cope with --amend or with squashed-# commits.-#-# The third example adds a Signed-off-by line to the message, that can-# still be edited. This is rarely a good idea.--case "$2,$3" in- merge,)- /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;;--# ,|template,)-# /usr/bin/perl -i.bak -pe '-# print "\n" . `git diff --cached --name-status -r`-# if /^#/ && $first++ == 0' "$1" ;;-- *) ;;-esac--# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')-# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
@@ -1,128 +0,0 @@-#!/bin/sh-#-# An example hook script to blocks unannotated tags from entering.-# Called by "git receive-pack" with arguments: refname sha1-old sha1-new-#-# To enable this hook, rename this file to "update".-#-# Config-# -------# hooks.allowunannotated-# This boolean sets whether unannotated tags will be allowed into the-# repository. By default they won't be.-# hooks.allowdeletetag-# This boolean sets whether deleting tags will be allowed in the-# repository. By default they won't be.-# hooks.allowmodifytag-# This boolean sets whether a tag may be modified after creation. By default-# it won't be.-# hooks.allowdeletebranch-# This boolean sets whether deleting branches will be allowed in the-# repository. By default they won't be.-# hooks.denycreatebranch-# This boolean sets whether remotely creating branches will be denied-# in the repository. By default this is allowed.-#--# --- Command line-refname="$1"-oldrev="$2"-newrev="$3"--# --- Safety check-if [ -z "$GIT_DIR" ]; then- echo "Don't run this script from the command line." >&2- echo " (if you want, you could supply GIT_DIR then run" >&2- echo " $0 <ref> <oldrev> <newrev>)" >&2- exit 1-fi--if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then- echo "Usage: $0 <ref> <oldrev> <newrev>" >&2- exit 1-fi--# --- Config-allowunannotated=$(git config --bool hooks.allowunannotated)-allowdeletebranch=$(git config --bool hooks.allowdeletebranch)-denycreatebranch=$(git config --bool hooks.denycreatebranch)-allowdeletetag=$(git config --bool hooks.allowdeletetag)-allowmodifytag=$(git config --bool hooks.allowmodifytag)--# check for no description-projectdesc=$(sed -e '1q' "$GIT_DIR/description")-case "$projectdesc" in-"Unnamed repository"* | "")- echo "*** Project description file hasn't been set" >&2- exit 1- ;;-esac--# --- Check types-# if $newrev is 0000...0000, it's a commit to delete a ref.-zero="0000000000000000000000000000000000000000"-if [ "$newrev" = "$zero" ]; then- newrev_type=delete-else- newrev_type=$(git cat-file -t $newrev)-fi--case "$refname","$newrev_type" in- refs/tags/*,commit)- # un-annotated tag- short_refname=${refname##refs/tags/}- if [ "$allowunannotated" != "true" ]; then- echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2- echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2- exit 1- fi- ;;- refs/tags/*,delete)- # delete tag- if [ "$allowdeletetag" != "true" ]; then- echo "*** Deleting a tag is not allowed in this repository" >&2- exit 1- fi- ;;- refs/tags/*,tag)- # annotated tag- if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1- then- echo "*** Tag '$refname' already exists." >&2- echo "*** Modifying a tag is not allowed in this repository." >&2- exit 1- fi- ;;- refs/heads/*,commit)- # branch- if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then- echo "*** Creating a branch is not allowed in this repository" >&2- exit 1- fi- ;;- refs/heads/*,delete)- # delete branch- if [ "$allowdeletebranch" != "true" ]; then- echo "*** Deleting a branch is not allowed in this repository" >&2- exit 1- fi- ;;- refs/remotes/*,commit)- # tracking branch- ;;- refs/remotes/*,delete)- # delete tracking branch- if [ "$allowdeletebranch" != "true" ]; then- echo "*** Deleting a tracking branch is not allowed in this repository" >&2- exit 1- fi- ;;- *)- # Anything else (is there anything else?)- echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2- exit 1- ;;-esac--# --- Finished-exit 0
binary file changed (352 → absent bytes)
@@ -1,6 +0,0 @@-# git ls-files --others --exclude-from=.git/info/exclude-# Lines that start with '#' are comments.-# For a project mostly in C, the following would be a good set of-# exclude patterns (uncomment them if you want to use them):-# *.[oa]-# *~
@@ -1,4 +0,0 @@-0000000000000000000000000000000000000000 2353b6eaeee1c824371c12bad78d4053ec52041d George Pollard <porges@porg.es> 1353548294 +1300 clone: from https://github.com/Porges/email-validate-hs.git-2353b6eaeee1c824371c12bad78d4053ec52041d e890c931c46c7b2a4f53db4edcdfbff25943bfb9 George Pollard <porges@porg.es> 1353548540 +1300 revert: Revert "Allow external use"-caabd581f6cf7157ce211a91112cf5047e527be7 6eb270b673a8ae8db8e5e2a5e765065933943523 George Pollard <porges@porg.es> 1353548588 +1300 revert: Revert "revert removal of eof marker"-6eb270b673a8ae8db8e5e2a5e765065933943523 2353b6eaeee1c824371c12bad78d4053ec52041d George Pollard <porges@porg.es> 1353548599 +1300 reset: moving to e890c931c46c7b2a4f53db4edcdfbff25943bfb9^
@@ -1,4 +0,0 @@-0000000000000000000000000000000000000000 2353b6eaeee1c824371c12bad78d4053ec52041d George Pollard <porges@porg.es> 1353548294 +1300 clone: from https://github.com/Porges/email-validate-hs.git-2353b6eaeee1c824371c12bad78d4053ec52041d e890c931c46c7b2a4f53db4edcdfbff25943bfb9 George Pollard <porges@porg.es> 1353548540 +1300 revert: Revert "Allow external use"-caabd581f6cf7157ce211a91112cf5047e527be7 6eb270b673a8ae8db8e5e2a5e765065933943523 George Pollard <porges@porg.es> 1353548588 +1300 revert: Revert "revert removal of eof marker"-6eb270b673a8ae8db8e5e2a5e765065933943523 2353b6eaeee1c824371c12bad78d4053ec52041d George Pollard <porges@porg.es> 1353548599 +1300 reset: moving to e890c931c46c7b2a4f53db4edcdfbff25943bfb9^
@@ -1,1 +0,0 @@-0000000000000000000000000000000000000000 2353b6eaeee1c824371c12bad78d4053ec52041d George Pollard <porges@porg.es> 1353548294 +1300 clone: from https://github.com/Porges/email-validate-hs.git
@@ -1,1 +0,0 @@-2353b6eaeee1c824371c12bad78d4053ec52041d 60436f01329fb1e98b20c972b8f2dff34a605e4f George Pollard <porges@porg.es> 1353548635 +1300 update by push
binary file changed (47 → absent bytes)
binary file changed (178 → absent bytes)
binary file changed (235 → absent bytes)
binary file changed (160 → absent bytes)
@@ -1,2 +0,0 @@-xRÁjÜ0í5ú9åP"9ñ²MYPºÝBIné]¶&¶@¶FYÈÇw¤µ7ôZ-üf43ï½ioàövýnÔnàíÁA['µ³F'ÏÉúñqjõQ8ÛâHo÷>¾¬P>YW"?¾owÐSê}<Woèc°÷Îéh÷¿8§ü»³éS>ÒèýAwçæ}JaSUs¼jïª mLD¢ Ódà0Ê'MÁGÛ$ZæÛùx\&ý/IÐqô,-à0vì@PúÀÜ(ÅwÌüúº õª® Aj£ iÑñj4uF¦cy-AI7ÖÙ´/£uL¨ÑlåÙÁ{¸Qä§Ø¢<ÙÄ¡GmÄÅ©|g¸på(ó/ËËß~jTëj_5^Eö¤òEÞ&êxy£NC8V°ÑÀCÔpy w°¾ #a±º×W¸ç¨ºQï¯ êmÏ¡¼ru©×õôEG®&Ö¢ l/#ÏÊàv¿1¦ 6CÚpYòl¬Úå=W³(þ,eö
binary file changed (56 → absent bytes)
binary file changed (160 → absent bytes)
binary file changed (5311 → absent bytes)
@@ -1,1 +0,0 @@-x+)JMU0¶d040031QKÌÉLI,IÕË(fÈ®³õ{ßϹà_ÛÌbUǪfwceï
binary file changed (47 → absent bytes)
binary file changed (88 → absent bytes)
binary file changed (197 → absent bytes)
binary file changed (5283 → absent bytes)
@@ -1,2 +0,0 @@-xOKj1ëÚ§xd[óü©¥»nKèÞØr27nÎߤsÒ I¹Íó©c~ê-õ S ì3¼º+É ² ìGWÌUNYd,!q}É5rYvÌls 6F¾û±)½£éè£Mh¡ýõ!·m±¼»àO!%zf7&ÿìøGÔpvÚèʹÝd¢V wÌ¢gèÆÏãi¡Õ³ÐÚ÷ç_[óòcv
binary file changed (56 → absent bytes)
binary file changed (161 → absent bytes)
@@ -1,1 +0,0 @@-xRÁjÜ0íY_1§J$g½¤ KJ·ÛR(ÍÒ ¹KÖÄÈÐÈ!ùøµö^k° ßfæ½7ÆoVÝãÞìµóòE{guFñ\ÎWªV7»z¿÷õðm½òÙùùõs»û}Ø =æ.¤sø!µûà½NVp¿!ósÊ¿a¸S6} t/ºÐcÔí¹ysÜTÕ¯mu"¡MH´a\=&ù¬)óѲ )aEÃ|ÛˤøÉÑ>qCzÒæ>@9¼e6áÏ÷-\¯ëZX¤&¹ÿ§·2ãÌûPÊÚ8ïò24¾FL®GVÔ3!£Ùʳ÷w°RÆÔ L¹Ì¡CmÅ´§ËåÏ¥ÍÌßn4ª }µ/¶ÌJ/ë";RÓ]Þt:"§Ñ-F,ëh4!ð(5\\À-\_BÔ°°µº·7¸ã¨Z©ôÀæO¡iñêR¯í\M>Ô¢ l¯$ÏÊàv¿1&m¶£GÚpYõÉ^µ¶]Í øI÷
binary file changed (459 → absent bytes)
binary file changed (809 → absent bytes)
binary file changed (160 → absent bytes)
binary file changed (5277 → absent bytes)
binary file changed (161 → absent bytes)
binary file changed (136 → absent bytes)
binary file changed (161 → absent bytes)
binary file changed (203 → absent bytes)
binary file changed (182 → absent bytes)
binary file changed (56 → absent bytes)
binary file changed (440 → absent bytes)
binary file changed (48 → absent bytes)
binary file changed (48 → absent bytes)
binary file changed (213 → absent bytes)
binary file changed (167 → absent bytes)
binary file changed (5281 → absent bytes)
@@ -1,2 +0,0 @@-# pack-refs with: peeled -2353b6eaeee1c824371c12bad78d4053ec52041d refs/remotes/origin/master
@@ -1,1 +0,0 @@-60436f01329fb1e98b20c972b8f2dff34a605e4f
@@ -1,1 +0,0 @@-ref: refs/remotes/origin/master
@@ -1,1 +0,0 @@-60436f01329fb1e98b20c972b8f2dff34a605e4f
@@ -1,324 +0,0 @@-{-# LANGUAGE CPP #-} -module Text.Email.Validate (addrSpec,isValid,validate,EmailAddress(..)) -where - -import Control.Arrow ((***)) -import qualified Data.Ranges as Range -import Data.Char (chr) - -#if MIN_VERSION_parsec(3,0,0) -import Text.Parsec -import Text.Parsec.Char -#else -import Text.ParserCombinators.Parsec -#endif - --- | Constructor does no checking for invalid emails, so use at own risk. -data EmailAddress = EmailAddress - { - localPart :: String, - domainPart :: String - } - -instance Show EmailAddress where - show (EmailAddress l d) = l ++ ('@' : d) - --- | Validates whether a particular string is an email address --- according to RFC5322. -isValid :: String -> Bool -isValid x = let result = validate x in - either (const False) (const True) result - -simply = (>> return ()) - --- | If you want to find out why a particular string is not --- an email address, use this! -validate :: String -> Either ParseError EmailAddress -validate x = case parse addrSpec "" x of - Right n -> Right $ EmailAddress local domain - where (local,at:domain) = splitAt (length x - n) x - Left e -> Left e - -#if MIN_VERSION_parsec(3,0,0) -addrSpec :: Parsec String () Int -#else -addrSpec :: CharParser () Int -#endif -addrSpec = do - localPartParser - s1 <- getInput - char '@' - domain - eof - return (length s1) - -localPartParser = dottedAtoms -domain = dottedAtoms <|> domainLiteral - -dottedAtoms = simply $ (optional cfws >> (atom <|> quotedString) >> optional cfws) - `sepBy1` (char '.') -atom = simply $ many1 atomText -atomText = simply $ alphaNum <|> oneOf "!#$%&'*+-/=?^_`{|}~" - -domainLiteral = between (optional cfws >> char '[') (char ']' >> optional cfws) $ - many (optional fws >> domainText) >> optional fws -domainText = ranges [(33,90),(94,126)] <|> obsNoWsCtl - - -quotedString = between (char '"') (char '"') $ - many (optional fws >> quotedContent) >> optional fws -quotedContent = quotedText <|> quotedPair -quotedText = ranges [(33,33),(35,91),(93,126)] <|> obsNoWsCtl -quotedPair = char '\\' >> (vchar <|> wsp <|> lf <|> cr <|> obsNoWsCtl <|> nullChar) - -fws = (many1 wsp >> optional (crlf >> many1 wsp)) - <|> (many1 (crlf >> many1 wsp) >> return ()) - -cfws = simply $ many (comment <|> fws) -comment = simply $ between (char '(') (char ')') $ - many (commentContent <|> fws) - -commentContent = commentText <|> quotedPair <|> comment -commentText = ranges [(33,39),(42,91),(93,126)] <|> obsNoWsCtl - -nullChar = simply $ char '\0' -wsp = simply $ oneOf " \t" -cr = simply $ char '\r' -lf = simply $ char '\n' -crlf = simply $ cr >> lf -vchar = ranges [(0x21,0x7e)] -obsNoWsCtl = ranges [(1,8),(11,12),(14,31),(127,127)] -ranges xs = simply $ satisfy (\c -> Range.inRanges c $ Range.ranges $ map (uncurry Range.range . (chr***chr)) $ xs) - -unitTest (x, y, z) = if isValid x == y then "" else (x ++": Should be "++show y ++", got "++show (not y)++"\n\t"++z++"\n") - -doSomeTests = do - putStr$unitTest("first.last@example.com", True, "") - putStr$unitTest("1234567890123456789012345678901234567890123456789012345678901234@example.com", True, "") - putStr$unitTest("\"first last\"@example.com", True, "") - putStr$unitTest("\"first\\\"last\"@example.com", True, "") - putStr$unitTest("first\\@last@example.com", False, "Escaping can only happen within a quoted string") - putStr$unitTest("\"first@last\"@example.com", True, "") - putStr$unitTest("\"first\\\\last\"@example.com", True, "") - putStr$unitTest("x@x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x234", True, "") - putStr$unitTest("123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.123456789012345678901234567890123456789012345678901234567890123.example.com", True, "") - putStr$unitTest("first.last@[12.34.56.78]", True, "") - putStr$unitTest("first.last@[IPv6:::12.34.56.78]", True, "") - putStr$unitTest("first.last@[IPv6:1111:2222:3333::4444:12.34.56.78]", True, "") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:12.34.56.78]", True, "") - putStr$unitTest("first.last@[IPv6:::1111:2222:3333:4444:5555:6666]", True, "") - putStr$unitTest("first.last@[IPv6:1111:2222:3333::4444:5555:6666]", True, "") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666::]", True, "") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]", True, "") - putStr$unitTest("first.last@x23456789012345678901234567890123456789012345678901234567890123.example.com", True, "") - putStr$unitTest("first.last@1xample.com", True, "") - putStr$unitTest("first.last@123.example.com", True, "") - putStr$unitTest("123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.1234.example.com", False, "Entire address is longer than 256 characters") - putStr$unitTest("first.last", False, "No @") - putStr$unitTest("12345678901234567890123456789012345678901234567890123456789012345@example.com", False, "Local part more than 64 characters") - putStr$unitTest(".first.last@example.com", False, "Local part starts with a dot") - putStr$unitTest("first.last.@example.com", False, "Local part ends with a dot") - putStr$unitTest("first..last@example.com", False, "Local part has consecutive dots") - putStr$unitTest("\"first\"last\"@example.com", False, "Local part contains unescaped excluded characters") - putStr$unitTest("\"first\\last\"@example.com", True, "Any character can be escaped in a quoted string") - putStr$unitTest("\"\"\"@example.com", False, "Local part contains unescaped excluded characters") - putStr$unitTest("\"\\\"@example.com", False, "Local part cannot end with a backslash") - putStr$unitTest("\"\"@example.com", False, "Local part is effectively empty") - putStr$unitTest("first\\\\@last@example.com", False, "Local part contains unescaped excluded characters") - putStr$unitTest("first.last@", False, "No domain") - putStr$unitTest("x@x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456", False, "Domain exceeds 255 chars") - putStr$unitTest("first.last@[.12.34.56.78]", False, "Only char that can precede IPv4 address is \':\'") - putStr$unitTest("first.last@[12.34.56.789]", False, "Can\'t be interpreted as IPv4 so IPv6 tag is missing") - putStr$unitTest("first.last@[::12.34.56.78]", False, "IPv6 tag is missing") - putStr$unitTest("first.last@[IPv5:::12.34.56.78]", False, "IPv6 tag is wrong") - putStr$unitTest("first.last@[IPv6:1111:2222:3333::4444:5555:12.34.56.78]", False, "Too many IPv6 groups (4 max)") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:12.34.56.78]", False, "Not enough IPv6 groups") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:12.34.56.78]", False, "Too many IPv6 groups (6 max)") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777]", False, "Not enough IPv6 groups") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]", False, "Too many IPv6 groups (8 max)") - putStr$unitTest("first.last@[IPv6:1111:2222::3333::4444:5555:6666]", False, "Too many \'::\' (can be none or one)") - putStr$unitTest("first.last@[IPv6:1111:2222:3333::4444:5555:6666:7777]", False, "Too many IPv6 groups (6 max)") - putStr$unitTest("first.last@[IPv6:1111:2222:333x::4444:5555]", False, "x is not valid in an IPv6 address") - putStr$unitTest("first.last@[IPv6:1111:2222:33333::4444:5555]", False, "33333 is not a valid group in an IPv6 address") - putStr$unitTest("first.last@example.123", False, "TLD can\'t be all digits") - putStr$unitTest("first.last@com", False, "Mail host must be second- or lower level") - putStr$unitTest("first.last@-xample.com", False, "Label can\'t begin with a hyphen") - putStr$unitTest("first.last@exampl-.com", False, "Label can\'t end with a hyphen") - putStr$unitTest("first.last@x234567890123456789012345678901234567890123456789012345678901234.example.com", False, "Label can\'t be longer than 63 octets") - putStr$unitTest("\"Abc\\@def\"@example.com", True, "") - putStr$unitTest("\"Fred\\ Bloggs\"@example.com", True, "") - putStr$unitTest("\"Joe.\\\\Blow\"@example.com", True, "") - putStr$unitTest("\"Abc@def\"@example.com", True, "") - putStr$unitTest("\"Fred Bloggs\"@example.com", True, "") - putStr$unitTest("user+mailbox@example.com", True, "") - putStr$unitTest("customer/department=shipping@example.com", True, "") - putStr$unitTest("$A12345@example.com", True, "") - putStr$unitTest("!def!xyz%abc@example.com", True, "") - putStr$unitTest("_somename@example.com", True, "") - putStr$unitTest("dclo@us.ibm.com", True, "") - putStr$unitTest("abc\\@def@example.com", False, "This example from RFC3696 was corrected in an erratum") - putStr$unitTest("abc\\\\@example.com", False, "This example from RFC3696 was corrected in an erratum") - putStr$unitTest("peter.piper@example.com", True, "") - putStr$unitTest("Doug\\ \\\"Ace\\\"\\ Lovell@example.com", False, "Escaping can only happen in a quoted string") - putStr$unitTest("\"Doug \\\"Ace\\\" L.\"@example.com", True, "") - putStr$unitTest("abc@def@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("abc\\\\@def@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("abc\\@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("@example.com", False, "No local part") - putStr$unitTest("doug@", False, "Doug Lovell says this should fail") - putStr$unitTest("\"qu@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("ote\"@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest(".dot@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("dot.@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("two..dot@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("\"Doug \"Ace\" L.\"@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("Doug\\ \\\"Ace\\\"\\ L\\.@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("hello world@example.com", False, "Doug Lovell says this should fail") - putStr$unitTest("gatsby@f.sc.ot.t.f.i.tzg.era.l.d.", False, "Doug Lovell says this should fail") - putStr$unitTest("test@example.com", True, "") - putStr$unitTest("TEST@example.com", True, "") - putStr$unitTest("1234567890@example.com", True, "") - putStr$unitTest("test+test@example.com", True, "") - putStr$unitTest("test-test@example.com", True, "") - putStr$unitTest("t*est@example.com", True, "") - putStr$unitTest("+1~1+@example.com", True, "") - putStr$unitTest("{_test_}@example.com", True, "") - putStr$unitTest("\"[[ test ]]\"@example.com", True, "") - putStr$unitTest("test.test@example.com", True, "") - putStr$unitTest("\"test.test\"@example.com", True, "") - putStr$unitTest("test.\"test\"@example.com", True, "Obsolete form, but documented in RFC2822") - putStr$unitTest("\"test@test\"@example.com", True, "") - putStr$unitTest("test@123.123.123.x123", True, "") - putStr$unitTest("test@123.123.123.123", False, "Top Level Domain won\'t be all-numeric (see RFC3696 Section 2). I disagree with Dave Child on this one.") - putStr$unitTest("test@[123.123.123.123]", True, "") - putStr$unitTest("test@example.example.com", True, "") - putStr$unitTest("test@example.example.example.com", True, "") - putStr$unitTest("test.example.com", False, "") - putStr$unitTest("test.@example.com", False, "") - putStr$unitTest("test..test@example.com", False, "") - putStr$unitTest(".test@example.com", False, "") - putStr$unitTest("test@test@example.com", False, "") - putStr$unitTest("test@@example.com", False, "") - putStr$unitTest("-- test --@example.com", False, "No spaces allowed in local part") - putStr$unitTest("[test]@example.com", False, "Square brackets only allowed within quotes") - putStr$unitTest("\"test\\test\"@example.com", True, "Any character can be escaped in a quoted string") - putStr$unitTest("\"test\"test\"@example.com", False, "Quotes cannot be nested") - putStr$unitTest("()[]\\;:,><@example.com", False, "Disallowed Characters") - putStr$unitTest("test@.", False, "Dave Child says so") - putStr$unitTest("test@example.", False, "Dave Child says so") - putStr$unitTest("test@.org", False, "Dave Child says so") - putStr$unitTest("test@123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012.com", False, "255 characters is maximum length for domain. This is 256.") - putStr$unitTest("test@example", False, "Dave Child says so") - putStr$unitTest("test@[123.123.123.123", False, "Dave Child says so") - putStr$unitTest("test@123.123.123.123]", False, "Dave Child says so") - putStr$unitTest("NotAnEmail", False, "Phil Haack says so") - putStr$unitTest("@NotAnEmail", False, "Phil Haack says so") - putStr$unitTest("\"test\\\\blah\"@example.com", True, "") - putStr$unitTest("\"test\\blah\"@example.com", True, "Any character can be escaped in a quoted string") - putStr$unitTest("\"test\\\rblah\"@example.com", True, "Quoted string specifically excludes carriage returns unless escaped") - putStr$unitTest("\"test\rblah\"@example.com", False, "Quoted string specifically excludes carriage returns") - putStr$unitTest("\"test\\\"blah\"@example.com", True, "") - putStr$unitTest("\"test\"blah\"@example.com", False, "Phil Haack says so") - putStr$unitTest("customer/department@example.com", True, "") - putStr$unitTest("_Yosemite.Sam@example.com", True, "") - putStr$unitTest("~@example.com", True, "") - putStr$unitTest(".wooly@example.com", False, "Phil Haack says so") - putStr$unitTest("wo..oly@example.com", False, "Phil Haack says so") - putStr$unitTest("pootietang.@example.com", False, "Phil Haack says so") - putStr$unitTest(".@example.com", False, "Phil Haack says so") - putStr$unitTest("\"Austin@Powers\"@example.com", True, "") - putStr$unitTest("Ima.Fool@example.com", True, "") - putStr$unitTest("\"Ima.Fool\"@example.com", True, "") - putStr$unitTest("\"Ima Fool\"@example.com", True, "") - putStr$unitTest("Ima Fool@example.com", False, "Phil Haack says so") - putStr$unitTest("phil.h\\@\\@ck@haacked.com", False, "Escaping can only happen in a quoted string") - putStr$unitTest("\"first\".\"last\"@example.com", True, "") - putStr$unitTest("\"first\".middle.\"last\"@example.com", True, "") - putStr$unitTest("\"first\\\\\"last\"@example.com", False, "Contains an unescaped quote") - putStr$unitTest("\"first\".last@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("first.\"last\"@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("\"first\".\"middle\".\"last\"@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("\"first.middle\".\"last\"@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("\"first.middle.last\"@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("\"first..last\"@example.com", True, "obs-local-part form as described in RFC 2822") - putStr$unitTest("foo@[\\1.2.3.4]", False, "RFC 5321 specifies the syntax for address-literal and does not allow escaping") - putStr$unitTest("\"first\\\\\\\"last\"@example.com", True, "") - putStr$unitTest("first.\"mid\\dle\".\"last\"@example.com", True, "Backslash can escape anything but must escape something") - putStr$unitTest("Test.\r\n Folding.\r\n Whitespace@example.com", True, "") - putStr$unitTest("first.\"\".last@example.com", False, "Contains a zero-length element") - putStr$unitTest("first\\last@example.com", False, "Unquoted string must be an atom") - putStr$unitTest("Abc\\@def@example.com", False, "Was incorrectly given as a valid address in the original RFC3696") - putStr$unitTest("Fred\\ Bloggs@example.com", False, "Was incorrectly given as a valid address in the original RFC3696") - putStr$unitTest("Joe.\\\\Blow@example.com", False, "Was incorrectly given as a valid address in the original RFC3696") - putStr$unitTest("first.last@[IPv6:1111:2222:3333:4444:5555:6666:12.34.567.89]", False, "IPv4 part contains an invalid octet") - putStr$unitTest("\"test\\\r\n blah\"@example.com", False, "Folding white space can\'t appear within a quoted pair") - putStr$unitTest("\"test\r\n blah\"@example.com", True, "This is a valid quoted string with folding white space") - putStr$unitTest("{^c\\@**Dog^}@cartoon.com", False, "This is a throwaway example from Doug Lovell\'s article. Actually it\'s not a valid address.") - putStr$unitTest("(foo)cal(bar)@(baz)iamcal.com(quux)", True, "A valid address containing comments") - putStr$unitTest("cal@iamcal(woo).(yay)com", True, "A valid address containing comments") - putStr$unitTest("\"foo\"(yay)@(hoopla)[1.2.3.4]", False, "Address literal can\'t be commented (RFC5321)") - putStr$unitTest("cal(woo(yay)hoopla)@iamcal.com", True, "A valid address containing comments") - putStr$unitTest("cal(foo\\@bar)@iamcal.com", True, "A valid address containing comments") - putStr$unitTest("cal(foo\\)bar)@iamcal.com", True, "A valid address containing comments and an escaped parenthesis") - putStr$unitTest("cal(foo(bar)@iamcal.com", False, "Unclosed parenthesis in comment") - putStr$unitTest("cal(foo)bar)@iamcal.com", False, "Too many closing parentheses") - putStr$unitTest("cal(foo\\)@iamcal.com", False, "Backslash at end of comment has nothing to escape") - putStr$unitTest("first().last@example.com", True, "A valid address containing an empty comment") - putStr$unitTest("first.(\r\n middle\r\n )last@example.com", True, "Comment with folding white space") - putStr$unitTest("first(12345678901234567890123456789012345678901234567890)last@(1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890)example.com", False, "Too long with comments, not too long without") - putStr$unitTest("first(Welcome to\r\n the (\"wonderful\" (!)) world\r\n of email)@example.com", True, "Silly example from my blog post") - putStr$unitTest("pete(his account)@silly.test(his host)", True, "Canonical example from RFC5322") - putStr$unitTest("c@(Chris\'s host.)public.example", True, "Canonical example from RFC5322") - putStr$unitTest("jdoe@machine(comment). example", True, "Canonical example from RFC5322") - putStr$unitTest("1234 @ local(blah) .machine .example", True, "Canonical example from RFC5322") - putStr$unitTest("first(middle)last@example.com", False, "Can\'t have a comment or white space except at an element boundary") - putStr$unitTest("first(abc.def).last@example.com", True, "Comment can contain a dot") - putStr$unitTest("first(a\"bc.def).last@example.com", True, "Comment can contain double quote") - putStr$unitTest("first.(\")middle.last(\")@example.com", True, "Comment can contain a quote") - putStr$unitTest("first(abc(\"def\".ghi).mno)middle(abc(\"def\".ghi).mno).last@(abc(\"def\".ghi).mno)example(abc(\"def\".ghi).mno).(abc(\"def\".ghi).mno)com(abc(\"def\".ghi).mno)", False, "Can\'t have comments or white space except at an element boundary") - putStr$unitTest("first(abc\\(def)@example.com", True, "Comment can contain quoted-pair") - putStr$unitTest("first.last@x(1234567890123456789012345678901234567890123456789012345678901234567890).com", True, "Label is longer than 63 octets, but not with comment removed") - putStr$unitTest("a(a(b(c)d(e(f))g)h(i)j)@example.com", True, "") - putStr$unitTest("a(a(b(c)d(e(f))g)(h(i)j)@example.com", False, "Braces are not properly matched") - putStr$unitTest("name.lastname@domain.com", True, "") - putStr$unitTest(".@", False, "") - putStr$unitTest("a@b", False, "") - putStr$unitTest("@bar.com", False, "") - putStr$unitTest("@@bar.com", False, "") - putStr$unitTest("a@bar.com", True, "") - putStr$unitTest("aaa.com", False, "") - putStr$unitTest("aaa@.com", False, "") - putStr$unitTest("aaa@.123", False, "") - putStr$unitTest("aaa@[123.123.123.123]", True, "") - putStr$unitTest("aaa@[123.123.123.123]a", False, "extra data outside ip") - putStr$unitTest("aaa@[123.123.123.333]", False, "not a valid IP") - putStr$unitTest("a@bar.com.", False, "") - putStr$unitTest("a@bar", False, "") - putStr$unitTest("a-b@bar.com", True, "") - putStr$unitTest("+@b.c", True, "TLDs can be any length") - putStr$unitTest("+@b.com", True, "") - putStr$unitTest("a@-b.com", False, "") - putStr$unitTest("a@b-.com", False, "") - putStr$unitTest("-@..com", False, "") - putStr$unitTest("-@a..com", False, "") - putStr$unitTest("a@b.co-foo.uk", True, "") - putStr$unitTest("\"hello my name is\"@stutter.com", True, "") - putStr$unitTest("\"Test \\\"Fail\\\" Ing\"@example.com", True, "") - putStr$unitTest("valid@special.museum", True, "") - putStr$unitTest("invalid@special.museum-", False, "") - putStr$unitTest("shaitan@my-domain.thisisminekthx", True, "Disagree with Paul Gregg here") - putStr$unitTest("test@...........com", False, "......") - putStr$unitTest("foobar@192.168.0.1", False, "ip need to be []") - putStr$unitTest("\"Joe\\\\Blow\"@example.com", True, "") - putStr$unitTest("Invalid \\\n Folding \\\n Whitespace@example.com", False, "This isn\'t FWS so Dominic Sayers says it\'s invalid") - putStr$unitTest("HM2Kinsists@(that comments are allowed)this.is.ok", True, "") - putStr$unitTest("user%uucp!path@somehost.edu", True, "") - putStr$unitTest("\"first(last)\"@example.com", True, "") - putStr$unitTest(" \r\n (\r\n x \r\n ) \r\n first\r\n ( \r\n x\r\n ) \r\n .\r\n ( \r\n x) \r\n last \r\n ( x \r\n ) \r\n @example.com", True, "") - putStr$unitTest("test.\r\n \r\n obs@syntax.com", True, "obs-fws allows multiple lines") - putStr$unitTest("test. \r\n \r\n obs@syntax.com", True, "obs-fws allows multiple lines (test 2: space before break)") - putStr$unitTest("test.\r\n\r\n obs@syntax.com", False, "obs-fws must have at least one WSP per line") - putStr$unitTest("\"null \\\0\"@char.com", True, "can have escaped null character") - putStr$unitTest("\"null \0\"@char.com", False, "cannot have unescaped null character") - -
@@ -1,5 +1,5 @@ name: email-validate -version: 0.3.2 +version: 1.0.0 license: BSD3 license-file: LICENSE author: George Pollard @@ -10,17 +10,31 @@ description: Validating an email address string against RFC 5322 build-type: Simple stability: experimental -cabal-version: >= 1.6 - +cabal-version: >= 1.8 source-repository head type: git location: git://github.com/Porges/email-validate-hs.git library - build-depends: base >= 2 && < 5, parsec >= 3.0 || == 2.1.*, ranges >= 0.2.2 + build-depends: base >= 4 && < 5, attoparsec >= 0.10.0, bytestring >= 0.9 ghc-options: -O2 - - extensions: CPP + hs-source-dirs: src exposed-modules: - Text.Email.Validate + Text.Email.Validate, Text.Email.Parser + +Test-Suite Main + type: exitcode-stdio-1.0 + main-is: Main.hs + hs-source-dirs: tests + test-module Test.Text.Email.Validate + x-uses-tf: true + build-depends: + base >= 4 && < 5, + HUnit >= 1.2 && < 2, + email-validate, + QuickCheck >= 2.4, + test-framework >= 0.4.1, + test-framework-quickcheck2, + test-framework-hunit, + bytestring >= 0.9
@@ -0,0 +1,100 @@+module Text.Email.Parser + (addrSpec + ,localPart + ,domainPart + ,EmailAddress + ,toByteString) +where + +import Control.Applicative + +import qualified Data.ByteString.Char8 as BS +import Data.ByteString (ByteString) + +import Data.Char (chr) +import Data.Attoparsec.ByteString.Char8 +import Data.Attoparsec.Combinator + +-- | Represents an email address. +data EmailAddress = EmailAddress ByteString ByteString deriving (Eq,Ord) + +instance Show EmailAddress where + show = BS.unpack . toByteString + +-- | Converts an email address back to a ByteString +toByteString (EmailAddress l d) = BS.concat [l, BS.singleton '@', d] + +-- | Extracts the local part of an email address. +localPart :: EmailAddress -> ByteString +localPart (EmailAddress local _) = local + +-- | Extracts the domain part of an email address. +domainPart :: EmailAddress -> ByteString +domainPart (EmailAddress _ domain) = domain + +-- | A parser for email addresses. +addrSpec = do + localPart <- local + char '@' + domainPart <- domain + endOfInput + return (EmailAddress localPart domainPart) + +local = dottedAtoms +domain = dottedAtoms <|> domainLiteral + +dottedAtoms = BS.intercalate (BS.singleton '.') <$> + (optional cfws *> (atom <|> quotedString) <* optional cfws) `sepBy1` (char '.') +atom = takeWhile1 isAtomText + +isAtomText x = isAlphaNum x || inClass "!#$%&'*+/=?^_`{|}~-" x + +domainLiteral = (BS.cons '[' . flip BS.snoc ']' . BS.concat) <$> (between (optional cfws *> char '[') (char ']' <* optional cfws) $ + many (optional fws >> takeWhile1 isDomainText) <* optional fws) +isDomainText x = inClass "\33-\90\94-\126" x || isObsNoWsCtl x + +quotedString = (\x -> BS.concat $ [BS.singleton '"', BS.concat x, BS.singleton '"']) <$> (between (char '"') (char '"') $ + many (optional fws >> quotedContent) <* optional fws) + +quotedContent = takeWhile1 isQuotedText <|> quotedPair +isQuotedText x = inClass "\33\35-\91\93-\126" x || isObsNoWsCtl x + +quotedPair = (BS.cons '\\' . BS.singleton) <$> (char '\\' *> (vchar <|> wsp <|> lf <|> cr <|> obsNoWsCtl <|> nullChar)) + +cfws = ignore $ many (comment <|> fws) + +fws :: Parser () +fws = ignore $ + ignore (wsp1 >> optional (crlf >> wsp1)) + <|> ignore (many1 (crlf >> wsp1)) + +ignore :: Parser a -> Parser () +ignore x = x >> return () + +between l r x = l *> x <* r + +comment :: Parser () +comment = ignore ((between (char '(') (char ')') $ + many (ignore commentContent <|> fws))) + +commentContent = skipWhile1 isCommentText <|> ignore quotedPair <|> comment +isCommentText x = inClass "\33-\39\42-\91\93-\126" x || isObsNoWsCtl x + +nullChar = char '\0' + +skipWhile1 x = satisfy x >> skipWhile x + +wsp1 = skipWhile1 isWsp +wsp = satisfy isWsp +isWsp x = x == ' ' || x == '\t' + +isAlphaNum x = isDigit x || isAlpha_ascii x +cr = char '\r' +lf = char '\n' +crlf = cr >> lf >> return () + +isVchar = inClass "\x21-\x7e" +vchar = satisfy isVchar + +isObsNoWsCtl = inClass "\1-\8\11-\12\14-\31\127" +obsNoWsCtl = satisfy isObsNoWsCtl
@@ -0,0 +1,39 @@+module Text.Email.Validate + (isValid + ,validate + ,emailAddress + ,canonicalizeEmail + ,EmailAddress -- re-exported + ,localPart + ,domainPart + ,toByteString) +where + +import qualified Data.ByteString.Char8 as BS +import Data.ByteString (ByteString) +import Data.Attoparsec (parseOnly) + +import Text.Email.Parser + +-- | Smart constructor for an email address +emailAddress :: ByteString -> Maybe EmailAddress +emailAddress x = + case validate x of + Left _ -> Nothing + Right em -> Just em + +-- | Checks that an email is valid and returns a version of it +-- where comments and whitespace have been removed. +canonicalizeEmail :: ByteString -> Maybe ByteString +canonicalizeEmail = fmap toByteString . emailAddress + +-- | Validates whether a particular string is an email address +-- according to RFC5322. +isValid :: ByteString -> Bool +isValid x = let result = validate x in + either (const False) (const True) result + +-- | If you want to find out *why* a particular string is not +-- an email address, use this. +validate :: ByteString -> Either String EmailAddress +validate = parseOnly addrSpec
@@ -0,0 +1,281 @@+module Main where + +import Data.Maybe (catMaybes) +import Text.Email.Validate +import Test.HUnit + +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as BS + +import Test.Framework as TF (defaultMain, testGroup, Test) +import Test.Framework.Providers.HUnit +import Test.Framework.Providers.QuickCheck2 (testProperty) + +import Test.QuickCheck + +main :: IO () +main = defaultMain tests + +tests :: [TF.Test] +tests = [ + testGroup "QuickCheck Text.Email.Validate" [ + testProperty "doubleCanonicalize" prop_doubleCanonicalize + ], + testGroup "Unit tests Text.Email.Validate" $ flip concatMap units + (\(em, valid, why) -> let email = BS.pack em + in + [ + testCase ("doubleCanonicalize '" ++ em ++ "'") (True @=? case emailAddress email of { Nothing -> True; Just ok -> prop_doubleCanonicalize ok }), + testCase ("validity test '" ++ em ++ "'") (valid @=? isValid email) + ]) + ] + +instance Arbitrary ByteString where + arbitrary = fmap BS.pack arbitrary + +instance Arbitrary EmailAddress where + arbitrary = do + local <- suchThat arbitrary (\x -> isEmail x (BS.pack "example.com")) + domain <- suchThat arbitrary (\x -> isEmail (BS.pack "example") x) + let email = makeEmailLike local domain + let (Just result) = emailAddress email + return result + +isEmail l d = isValid (makeEmailLike l d) + +makeEmailLike l d = BS.concat [l, BS.singleton '@', d] + +prop_doubleCanonicalize email = Just email == emailAddress (toByteString email) + +--unitTest (x, y, z) = if not (isValid (BS.pack x) == y) then "" else (x ++" became "++ (case emailAddress (BS.pack x) of {Nothing -> "fail"; Just em -> show em}) ++": Should be "++show y ++", got "++show (not y)++"\n\t"++z++"\n") + +units = [ + ("first.last@example.com", True, ""), + ("1234567890123456789012345678901234567890123456789012345678901234@example.com", True, ""), + ("\"first last\"@example.com", True, ""), + ("\"first\\\"last\"@example.com", True, ""), + ("first\\@last@example.com", False, "Escaping can only happen within a quoted string"), + ("\"first@last\"@example.com", True, ""), + ("\"first\\\\last\"@example.com", True, ""), + ("x@x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x234", True, ""), + ("123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.123456789012345678901234567890123456789012345678901234567890123.example.com", True, ""), + ("first.last@[12.34.56.78]", True, ""), + ("first.last@[IPv6:::12.34.56.78]", True, ""), + ("first.last@[IPv6:1111:2222:3333::4444:12.34.56.78]", True, ""), + ("first.last@[IPv6:1111:2222:3333:4444:5555:6666:12.34.56.78]", True, ""), + ("first.last@[IPv6:::1111:2222:3333:4444:5555:6666]", True, ""), + ("first.last@[IPv6:1111:2222:3333::4444:5555:6666]", True, ""), + ("first.last@[IPv6:1111:2222:3333:4444:5555:6666::]", True, ""), + ("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]", True, ""), + ("first.last@x23456789012345678901234567890123456789012345678901234567890123.example.com", True, ""), + ("first.last@1xample.com", True, ""), + ("first.last@123.example.com", True, ""), + ("first.last", False, "No @"), + (".first.last@example.com", False, "Local part starts with a dot"), + ("first.last.@example.com", False, "Local part ends with a dot"), + ("first..last@example.com", False, "Local part has consecutive dots"), + ("\"first\"last\"@example.com", False, "Local part contains unescaped excluded characters"), + ("\"first\\last\"@example.com", True, "Any character can be escaped in a quoted string"), + ("\"\"\"@example.com", False, "Local part contains unescaped excluded characters"), + ("\"\\\"@example.com", False, "Local part cannot end with a backslash"), + ("first\\\\@last@example.com", False, "Local part contains unescaped excluded characters"), + ("first.last@", False, "No domain"), + ("\"Abc\\@def\"@example.com", True, ""), + ("\"Fred\\ Bloggs\"@example.com", True, ""), + ("\"Joe.\\\\Blow\"@example.com", True, ""), + ("\"Abc@def\"@example.com", True, ""), + ("\"Fred Bloggs\"@example.com", True, ""), + ("user+mailbox@example.com", True, ""), + ("customer/department=shipping@example.com", True, ""), + ("$A12345@example.com", True, ""), + ("!def!xyz%abc@example.com", True, ""), + ("_somename@example.com", True, ""), + ("dclo@us.ibm.com", True, ""), + ("abc\\@def@example.com", False, "This example from RFC3696 was corrected in an erratum"), + ("abc\\\\@example.com", False, "This example from RFC3696 was corrected in an erratum"), + ("peter.piper@example.com", True, ""), + ("Doug\\ \\\"Ace\\\"\\ Lovell@example.com", False, "Escaping can only happen in a quoted string"), + ("\"Doug \\\"Ace\\\" L.\"@example.com", True, ""), + ("abc@def@example.com", False, "Doug Lovell says this should fail"), + ("abc\\\\@def@example.com", False, "Doug Lovell says this should fail"), + ("abc\\@example.com", False, "Doug Lovell says this should fail"), + ("@example.com", False, "No local part"), + ("doug@", False, "Doug Lovell says this should fail"), + ("\"qu@example.com", False, "Doug Lovell says this should fail"), + ("ote\"@example.com", False, "Doug Lovell says this should fail"), + (".dot@example.com", False, "Doug Lovell says this should fail"), + ("dot.@example.com", False, "Doug Lovell says this should fail"), + ("two..dot@example.com", False, "Doug Lovell says this should fail"), + ("\"Doug \"Ace\" L.\"@example.com", False, "Doug Lovell says this should fail"), + ("Doug\\ \\\"Ace\\\"\\ L\\.@example.com", False, "Doug Lovell says this should fail"), + ("hello world@example.com", False, "Doug Lovell says this should fail"), + ("gatsby@f.sc.ot.t.f.i.tzg.era.l.d.", False, "Doug Lovell says this should fail"), + ("test@example.com", True, ""), + ("TEST@example.com", True, ""), + ("1234567890@example.com", True, ""), + ("test+test@example.com", True, ""), + ("test-test@example.com", True, ""), + ("t*est@example.com", True, ""), + ("+1~1+@example.com", True, ""), + ("{_test_}@example.com", True, ""), + ("\"[[ test ]]\"@example.com", True, ""), + ("test.test@example.com", True, ""), + ("\"test.test\"@example.com", True, ""), + ("test.\"test\"@example.com", True, "Obsolete form, but documented in RFC2822"), + ("\"test@test\"@example.com", True, ""), + ("test@123.123.123.x123", True, ""), + ("test@[123.123.123.123]", True, ""), + ("test@example.example.com", True, ""), + ("test@example.example.example.com", True, ""), + ("test.example.com", False, ""), + ("test.@example.com", False, ""), + ("test..test@example.com", False, ""), + (".test@example.com", False, ""), + ("test@test@example.com", False, ""), + ("test@@example.com", False, ""), + ("-- test --@example.com", False, "No spaces allowed in local part"), + ("[test]@example.com", False, "Square brackets only allowed within quotes"), + ("\"test\\test\"@example.com", True, "Any character can be escaped in a quoted string"), + ("\"test\"test\"@example.com", False, "Quotes cannot be nested"), + ("()[]\\;:,><@example.com", False, "Disallowed Characters"), + ("test@.", False, "Dave Child says so"), + ("test@example.", False, "Dave Child says so"), + ("test@.org", False, "Dave Child says so"), + ("test@[123.123.123.123", False, "Dave Child says so"), + ("test@123.123.123.123]", False, "Dave Child says so"), + ("NotAnEmail", False, "Phil Haack says so"), + ("@NotAnEmail", False, "Phil Haack says so"), + ("\"test\\\\blah\"@example.com", True, ""), + ("\"test\\blah\"@example.com", True, "Any character can be escaped in a quoted string"), + ("\"test\\\rblah\"@example.com", True, "Quoted string specifically excludes carriage returns unless escaped"), + ("\"test\rblah\"@example.com", False, "Quoted string specifically excludes carriage returns"), + ("\"test\\\"blah\"@example.com", True, ""), + ("\"test\"blah\"@example.com", False, "Phil Haack says so"), + ("customer/department@example.com", True, ""), + ("_Yosemite.Sam@example.com", True, ""), + ("~@example.com", True, ""), + (".wooly@example.com", False, "Phil Haack says so"), + ("wo..oly@example.com", False, "Phil Haack says so"), + ("pootietang.@example.com", False, "Phil Haack says so"), + (".@example.com", False, "Phil Haack says so"), + ("\"Austin@Powers\"@example.com", True, ""), + ("Ima.Fool@example.com", True, ""), + ("\"Ima.Fool\"@example.com", True, ""), + ("\"Ima Fool\"@example.com", True, ""), + ("Ima Fool@example.com", False, "Phil Haack says so"), + ("phil.h\\@\\@ck@haacked.com", False, "Escaping can only happen in a quoted string"), + ("\"first\".\"last\"@example.com", True, ""), + ("\"first\".middle.\"last\"@example.com", True, ""), + ("\"first\\\\\"last\"@example.com", False, "Contains an unescaped quote"), + ("\"first\".last@example.com", True, "obs-local-part form as described in RFC 2822"), + ("first.\"last\"@example.com", True, "obs-local-part form as described in RFC 2822"), + ("\"first\".\"middle\".\"last\"@example.com", True, "obs-local-part form as described in RFC 2822"), + ("\"first.middle\".\"last\"@example.com", True, "obs-local-part form as described in RFC 2822"), + ("\"first.middle.last\"@example.com", True, "obs-local-part form as described in RFC 2822"), + ("\"first..last\"@example.com", True, "obs-local-part form as described in RFC 2822"), + ("foo@[\\1.2.3.4]", False, "RFC 5321 specifies the syntax for address-literal and does not allow escaping"), + ("\"first\\\\\\\"last\"@example.com", True, ""), + ("first.\"mid\\dle\".\"last\"@example.com", True, "Backslash can escape anything but must escape something"), + ("Test.\r\n Folding.\r\n Whitespace@example.com", True, ""), + ("first\\last@example.com", False, "Unquoted string must be an atom"), + ("Abc\\@def@example.com", False, "Was incorrectly given as a valid address in the original RFC3696"), + ("Fred\\ Bloggs@example.com", False, "Was incorrectly given as a valid address in the original RFC3696"), + ("Joe.\\\\Blow@example.com", False, "Was incorrectly given as a valid address in the original RFC3696"), + ("\"test\\\r\n blah\"@example.com", False, "Folding white space can\'t appear within a quoted pair"), + ("\"test\r\n blah\"@example.com", True, "This is a valid quoted string with folding white space"), + ("{^c\\@**Dog^}@cartoon.com", False, "This is a throwaway example from Doug Lovell\'s article. Actually it\'s not a valid address."), + ("(foo)cal(bar)@(baz)iamcal.com(quux)", True, "A valid address containing comments"), + ("cal@iamcal(woo).(yay)com", True, "A valid address containing comments"), + ("cal(woo(yay)hoopla)@iamcal.com", True, "A valid address containing comments"), + ("cal(foo\\@bar)@iamcal.com", True, "A valid address containing comments"), + ("cal(foo\\)bar)@iamcal.com", True, "A valid address containing comments and an escaped parenthesis"), + ("cal(foo(bar)@iamcal.com", False, "Unclosed parenthesis in comment"), + ("cal(foo)bar)@iamcal.com", False, "Too many closing parentheses"), + ("cal(foo\\)@iamcal.com", False, "Backslash at end of comment has nothing to escape"), + ("first().last@example.com", True, "A valid address containing an empty comment"), + ("first.(\r\n middle\r\n )last@example.com", True, "Comment with folding white space"), + ("first(12345678901234567890123456789012345678901234567890)last@(1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890)example.com", False, "Too long with comments, not too long without"), + ("first(Welcome to\r\n the (\"wonderful\" (!)) world\r\n of email)@example.com", True, "Silly example from my blog post"), + ("pete(his account)@silly.test(his host)", True, "Canonical example from RFC5322"), + ("c@(Chris\'s host.)public.example", True, "Canonical example from RFC5322"), + ("jdoe@machine(comment). example", True, "Canonical example from RFC5322"), + ("1234 @ local(blah) .machine .example", True, "Canonical example from RFC5322"), + ("first(middle)last@example.com", False, "Can\'t have a comment or white space except at an element boundary"), + ("first(abc.def).last@example.com", True, "Comment can contain a dot"), + ("first(a\"bc.def).last@example.com", True, "Comment can contain double quote"), + ("first.(\")middle.last(\")@example.com", True, "Comment can contain a quote"), + ("first(abc(\"def\".ghi).mno)middle(abc(\"def\".ghi).mno).last@(abc(\"def\".ghi).mno)example(abc(\"def\".ghi).mno).(abc(\"def\".ghi).mno)com(abc(\"def\".ghi).mno)", False, "Can\'t have comments or white space except at an element boundary"), + ("first(abc\\(def)@example.com", True, "Comment can contain quoted-pair"), + ("first.last@x(1234567890123456789012345678901234567890123456789012345678901234567890).com", True, "Label is longer than 63 octets, but not with comment removed"), + ("a(a(b(c)d(e(f))g)h(i)j)@example.com", True, ""), + ("a(a(b(c)d(e(f))g)(h(i)j)@example.com", False, "Braces are not properly matched"), + ("name.lastname@domain.com", True, ""), + (".@", False, ""), + ("@bar.com", False, ""), + ("@@bar.com", False, ""), + ("a@bar.com", True, ""), + ("aaa.com", False, ""), + ("aaa@.com", False, ""), + ("aaa@.123", False, ""), + ("aaa@[123.123.123.123]", True, ""), + ("aaa@[123.123.123.123]a", False, "extra data outside ip"), + ("a@bar.com.", False, ""), + ("a-b@bar.com", True, ""), + ("+@b.c", True, "TLDs can be any length"), + ("+@b.com", True, ""), + ("-@..com", False, ""), + ("-@a..com", False, ""), + ("a@b.co-foo.uk", True, ""), + ("\"hello my name is\"@stutter.com", True, ""), + ("\"Test \\\"Fail\\\" Ing\"@example.com", True, ""), + ("valid@special.museum", True, ""), + ("shaitan@my-domain.thisisminekthx", True, "Disagree with Paul Gregg here"), + ("test@...........com", False, "......"), + ("\"Joe\\\\Blow\"@example.com", True, ""), + ("Invalid \\\n Folding \\\n Whitespace@example.com", False, "This isn\'t FWS so Dominic Sayers says it\'s invalid"), + ("HM2Kinsists@(that comments are allowed)this.is.ok", True, ""), + ("user%uucp!path@somehost.edu", True, ""), + ("\"first(last)\"@example.com", True, ""), + (" \r\n (\r\n x \r\n ) \r\n first\r\n ( \r\n x\r\n ) \r\n .\r\n ( \r\n x) \r\n last \r\n ( x \r\n ) \r\n @example.com", True, ""), + ("test.\r\n \r\n obs@syntax.com", True, "obs-fws allows multiple lines"), + ("test. \r\n \r\n obs@syntax.com", True, "obs-fws allows multiple lines (test 2: space before break)"), + ("test.\r\n\r\n obs@syntax.com", False, "obs-fws must have at least one WSP per line"), + ("\"null \\\0\"@char.com", True, "can have escaped null character"), + ("\"null \0\"@char.com", False, "cannot have unescaped null character") + -- items below here are invalid according to other RFCs (or opinions) + --("\"\"@example.com", False, "Local part is effectively empty"), + --("foobar@192.168.0.1", False, "ip need to be []"), + --("first.last@[.12.34.56.78]", False, "Only char that can precede IPv4 address is \':\'"), + --("first.last@[12.34.56.789]", False, "Can\'t be interpreted as IPv4 so IPv6 tag is missing"), + --("first.last@[::12.34.56.78]", False, "IPv6 tag is missing"), + --("first.last@[IPv5:::12.34.56.78]", False, "IPv6 tag is wrong"), + --("first.last@[IPv6:1111:2222:3333::4444:5555:12.34.56.78]", False, "Too many IPv6 groups (4 max)"), + --("first.last@[IPv6:1111:2222:3333:4444:5555:12.34.56.78]", False, "Not enough IPv6 groups"), + --("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:12.34.56.78]", False, "Too many IPv6 groups (6 max)"), + --("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777]", False, "Not enough IPv6 groups"), + --("first.last@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]", False, "Too many IPv6 groups (8 max)"), + --("first.last@[IPv6:1111:2222::3333::4444:5555:6666]", False, "Too many \'::\' (can be none or one)"), + --("first.last@[IPv6:1111:2222:3333::4444:5555:6666:7777]", False, "Too many IPv6 groups (6 max)"), + --("first.last@[IPv6:1111:2222:333x::4444:5555]", False, "x is not valid in an IPv6 address"), + --("first.last@[IPv6:1111:2222:33333::4444:5555]", False, "33333 is not a valid group in an IPv6 address"), + --("first.last@example.123", False, "TLD can\'t be all digits"), + --("aaa@[123.123.123.333]", False, "not a valid IP"), + --("first.last@[IPv6:1111:2222:3333:4444:5555:6666:12.34.567.89]", False, "IPv4 part contains an invalid octet"), + --("a@b", False, ""), + --("a@bar", False, ""), + --("invalid@special.museum-", False, ""), + --("a@-b.com", False, ""), + --("a@b-.com", False, ""), + --("\"foo\"(yay)@(hoopla)[1.2.3.4]", False, "Address literal can\'t be commented (RFC5321)"), + --("first.\"\".last@example.com", False, "Contains a zero-length element"), + --("test@example", False, "Dave Child says so"), + --("12345678901234567890123456789012345678901234567890123456789012345@example.com", False, "Local part more than 64 characters"), + --("x@x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456789.x23456", False, "Domain exceeds 255 chars"), + --("test@123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012.com", False, "255 characters is maximum length for domain. This is 256."), + --("123456789012345678901234567890123456789012345678901234567890@12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.12345678901234567890123456789012345678901234567890123456789.1234.example.com", False, "Entire address is longer than 256 characters"), + --("test@123.123.123.123", False, "Top Level Domain won\'t be all-numeric (see RFC3696 Section 2). I disagree with Dave Child on this one."), + --("first.last@x234567890123456789012345678901234567890123456789012345678901234.example.com", False, "Label can\'t be longer than 63 octets"), + --("first.last@com", False, "Mail host must be second- or lower level"), + --("first.last@-xample.com", False, "Label can\'t begin with a hyphen"), + --("first.last@exampl-.com", False, "Label can\'t end with a hyphen"), + ]