-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Make email validation same or stricter as Django (#36)
- Loading branch information
Showing
4 changed files
with
139 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
libpretixsync/src/main/java/eu/pretix/libpretixsync/utils/EmailValidator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package eu.pretix.libpretixsync.utils | ||
|
||
import java.lang.IllegalArgumentException | ||
import java.net.IDN | ||
import java.util.regex.Pattern | ||
|
||
class EmailValidator { | ||
// Same validation as django/core/validators.py | ||
val DOT_ATOM = "^[-!#\\$%&'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#\\$%&'*+/=?^_`{}|~0-9A-Z]+)*\\Z" | ||
val QUOTED_STRING = "^\"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\0177]|\\\\[\\001-\\011\\013\\014\\016-\\0177])*\"\\Z" | ||
val USER_REGEX = Pattern.compile("($DOT_ATOM|$QUOTED_STRING)", Pattern.CASE_INSENSITIVE) | ||
val DOMAIN_REGEX = Pattern.compile( | ||
// max length for domain name labels is 63 characters per RFC 1034 | ||
"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\\Z", | ||
Pattern.CASE_INSENSITIVE | ||
) | ||
|
||
fun isValidEmail(value: String): Boolean { | ||
// The maximum length of an email is 320 characters per RFC 3696 | ||
// section 3. | ||
if (value.isBlank() || !value.contains("@") || value.length > 320) { | ||
return false | ||
} | ||
|
||
val userPart = value.substringBeforeLast("@") | ||
val domainPart = value.substringAfterLast("@") | ||
|
||
if (!USER_REGEX.matcher(userPart).matches()) { | ||
return false | ||
} | ||
|
||
if (!validateDomainPart(domainPart)) { | ||
try { | ||
val asciiDomainPart = IDN.toASCII(domainPart) | ||
if (validateDomainPart(asciiDomainPart)) { | ||
return true | ||
} | ||
} catch (e: IllegalArgumentException) { | ||
// ignore | ||
} | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
fun validateDomainPart(domainPart: String): Boolean { | ||
return DOMAIN_REGEX.matcher(domainPart).matches() | ||
// Django also checks for literal form here, such as @[127.0.0.1] here, but we are stricter | ||
// here and do not accept that | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,8 +69,10 @@ class QuestionAnswerValidationTest(private val questionType: QuestionType, priva | |
arrayOf(QuestionType.W, "2016-02-29T14:30", "2016-02-29T14:30"), | ||
arrayOf(QuestionType.W, "2016-02-29T25:59", null), | ||
arrayOf(QuestionType.W, "2017-02-01", null), | ||
arrayOf(QuestionType.W, "fooobar", null) | ||
// TODO: Date, time, datetime | ||
arrayOf(QuestionType.W, "fooobar", null), | ||
arrayOf(QuestionType.EMAIL, "[email protected]", "[email protected]"), | ||
arrayOf(QuestionType.EMAIL, "foobar", null), | ||
arrayOf(QuestionType.EMAIL, "[email protected]", null) | ||
) | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
libpretixsync/src/test/java/eu/pretix/libpretixsync/utils/EmailValidatorTests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package eu.pretix.libpretixsync.utils | ||
|
||
import org.junit.Assert.assertEquals | ||
import org.junit.Test | ||
import org.junit.runner.RunWith | ||
import org.junit.runners.Parameterized | ||
|
||
@RunWith(Parameterized::class) | ||
class EmailValidatorTests(private val input: String, private val result: Boolean) { | ||
|
||
@Test | ||
fun test() { | ||
assertEquals(input, result, EmailValidator().isValidEmail(input)) | ||
} | ||
|
||
companion object { | ||
|
||
@JvmStatic | ||
@Parameterized.Parameters | ||
fun data() = listOf( | ||
// These are from https://github.com/django/django/blob/1b5338d03ecc962af8ab4678426bc60b0672b8dd/tests/validators/tests.py#L279 | ||
arrayOf("[email protected]", true), | ||
arrayOf("[email protected]", true), | ||
arrayOf("[email protected]", true), | ||
arrayOf("[email protected]", true), | ||
arrayOf("[email protected].उदाहरण.परीक्षा", true), | ||
arrayOf( | ||
"email@localhost", | ||
false | ||
), // difference to django since we did not implement the whitelist | ||
arrayOf("\"test@test\"@example.com", true), | ||
arrayOf("example@atm.${"a".repeat(63)}", true), | ||
arrayOf("example@${"a".repeat(63)}.atm", true), | ||
arrayOf("example@${"a".repeat(63)}.${"b".repeat(10)}.atm", true), | ||
arrayOf("example@atm.${"a".repeat(64)}", false), | ||
arrayOf("example@${"b".repeat(64)}.atm.${"a".repeat(63)}", false), | ||
arrayOf("example@${("a".repeat(63) + ".").repeat(100)}com", false), | ||
arrayOf("", false), | ||
arrayOf("abc", false), | ||
arrayOf("abc@", false), | ||
arrayOf("abc@bar", false), | ||
arrayOf("a @x.cz", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("something@@somewhere.com", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("[email protected]\n\n<script src=\"x.js\">", false), | ||
// Quoted-string format (CR not allowed) | ||
arrayOf("\"\\\t\"@here.com", true), | ||
arrayOf("\"\\\r\"@here.com", false), | ||
arrayOf("[email protected].", false), | ||
// Max length of domain name labels is 63 characters per RFC 1034. | ||
arrayOf("a@${"a".repeat(63)}.us", true), | ||
arrayOf("a@${"a".repeat(64)}.us", false), | ||
// Trailing newlines in username or domain not allowed | ||
arrayOf("[email protected]\n", false), | ||
arrayOf("a\n@b.com", false), | ||
arrayOf("\"test@test\"\n@example.com", false), | ||
|
||
// We are even stricter than Django and do not allow any IP addresses | ||
arrayOf("email@[127.0.0.1]", false), | ||
arrayOf("email@[2001:dB8::1]", false), | ||
arrayOf("email@[2001:dB8:0:0:0:0:0:1]", false), | ||
arrayOf("email@[::fffF:127.0.0.1]", false), | ||
arrayOf("[email protected]", false), | ||
arrayOf("email@[127.0.0.256]", false), | ||
arrayOf("email@[2001:db8::12345]", false), | ||
arrayOf("email@[2001:db8:0:0:0:0:1]", false), | ||
arrayOf("email@[::ffff:127.0.0.256]", false), | ||
arrayOf("email@[2001:dg8::1]", false), | ||
arrayOf("email@[2001:dG8:0:0:0:0:0:1]", false), | ||
arrayOf("email@[::fTzF:127.0.0.1]", false), | ||
arrayOf("a@[127.0.0.1]\n", false), | ||
|
||
// Real-world find | ||
arrayOf("[email protected]", false), | ||
) | ||
} | ||
} |