135 lines
4.9 KiB
Scala
135 lines
4.9 KiB
Scala
package lila.common
|
|
|
|
import scala.concurrent.duration._
|
|
|
|
case class ApiVersion(value: Int) extends AnyVal with IntValue with Ordered[ApiVersion] {
|
|
def compare(other: ApiVersion) = Integer.compare(value, other.value)
|
|
def gt(other: Int) = value > other
|
|
def gte(other: Int) = value >= other
|
|
def puzzleV2 = value >= 6
|
|
}
|
|
|
|
case class AssetVersion(value: String) extends AnyVal with StringValue
|
|
|
|
object AssetVersion {
|
|
var current = random
|
|
def change() = { current = random }
|
|
private def random = AssetVersion(ornicar.scalalib.Random secureString 6)
|
|
}
|
|
|
|
case class IsMobile(value: Boolean) extends AnyVal with BooleanValue
|
|
|
|
case class IpAddress(value: String) extends AnyVal with StringValue
|
|
|
|
object IpAddress {
|
|
// http://stackoverflow.com/questions/106179/regular-expression-to-match-hostname-or-ip-address
|
|
private val ipv4Regex =
|
|
"""^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$""".r
|
|
// ipv6 address in standard form (no compression, no leading zeros)
|
|
private val ipv6Regex = """^((0|[1-9a-f][0-9a-f]{0,3}+):){7}(0|[1-9a-f][0-9a-f]{0,3})""".r
|
|
|
|
def isv4(a: IpAddress) = ipv4Regex matches a.value
|
|
def isv6(a: IpAddress) = ipv6Regex matches a.value
|
|
|
|
def from(str: String): Option[IpAddress] = {
|
|
ipv4Regex.matches(str) || ipv6Regex.matches(str)
|
|
} option IpAddress(str)
|
|
}
|
|
|
|
case class NormalizedEmailAddress(value: String) extends AnyVal with StringValue
|
|
|
|
case class EmailAddress(value: String) extends AnyVal with StringValue {
|
|
def conceal =
|
|
value split '@' match {
|
|
case Array(user, domain) => s"${user take 3}*****@$domain"
|
|
case _ => value
|
|
}
|
|
|
|
def normalize =
|
|
NormalizedEmailAddress {
|
|
// changing normalization requires database migration!
|
|
val lower = value.toLowerCase
|
|
lower.split('@') match {
|
|
case Array(name, domain) if EmailAddress.gmailLikeNormalizedDomains(domain) =>
|
|
val normalizedName = name
|
|
.replace(".", "") // remove all dots
|
|
.takeWhile('+' !=) // skip everything after the first '+'
|
|
if (normalizedName.isEmpty) lower else s"$normalizedName@$domain"
|
|
case _ => lower
|
|
}
|
|
}
|
|
|
|
def domain: Option[Domain] =
|
|
value split '@' match {
|
|
case Array(_, domain) => Domain from domain.toLowerCase
|
|
case _ => none
|
|
}
|
|
|
|
def similarTo(other: EmailAddress) = normalize == other.normalize
|
|
|
|
def isNoReply = EmailAddress isNoReply value
|
|
def isSendable = !isNoReply
|
|
|
|
// safer logs
|
|
override def toString = "EmailAddress(****)"
|
|
}
|
|
|
|
object EmailAddress {
|
|
|
|
private val regex =
|
|
"""^[a-zA-Z0-9\.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$""".r
|
|
|
|
// adding normalized domains requires database migration!
|
|
private val gmailLikeNormalizedDomains =
|
|
Set("gmail.com", "googlemail.com", "protonmail.com", "protonmail.ch", "pm.me")
|
|
|
|
private def hasDotAt(str: String) = str contains ".@" // mailgun will reject it
|
|
private def hasConsecutiveDots(str: String) = str contains ".." // mailgun will reject it
|
|
private def startsWithDot(str: String) = str startsWith "." // mailgun will reject it
|
|
|
|
def matches(str: String): Boolean =
|
|
regex.find(str) &&
|
|
!hasDotAt(str) &&
|
|
!hasConsecutiveDots(str) &&
|
|
!startsWithDot(str)
|
|
|
|
def from(str: String): Option[EmailAddress] =
|
|
matches(str) option EmailAddress(str)
|
|
|
|
private def isNoReply(str: String) = str.startsWith("noreply.") && str.endsWith("@lichess.org")
|
|
}
|
|
|
|
case class Domain private (value: String) extends AnyVal with StringValue {
|
|
// heuristic to remove user controlled subdomain tails:
|
|
// tail.domain.com, tail.domain.co.uk, tail.domain.edu.au, etc.
|
|
def withoutSubdomain: Option[Domain] =
|
|
value.split('.').toList.reverse match {
|
|
case tld :: sld :: tail :: _ if sld.lengthIs <= 3 => Domain from s"$tail.$sld.$tld"
|
|
case tld :: sld :: _ => Domain from s"$sld.$tld"
|
|
case _ => none
|
|
}
|
|
def lower = Domain.Lower(value.toLowerCase)
|
|
}
|
|
|
|
object Domain {
|
|
// https://stackoverflow.com/a/26987741/1744715
|
|
private val regex =
|
|
"""^(((?!-))(xn--|_{1,1})?[a-z0-9-]{0,61}[a-z0-9]{1,1}\.)*(xn--)?([a-z0-9][a-z0-9\-]{0,60}|[a-z0-9-]{1,30}\.[a-z]{2,})$""".r
|
|
def isValid(str: String) = regex.matches(str)
|
|
def from(str: String): Option[Domain] = isValid(str) option Domain(str)
|
|
def unsafe(str: String): Domain = Domain(str)
|
|
|
|
case class Lower(value: String) extends AnyVal with StringValue {
|
|
def domain = Domain(value)
|
|
}
|
|
}
|
|
|
|
case class Strings(value: List[String]) extends AnyVal
|
|
case class UserIds(value: List[String]) extends AnyVal
|
|
case class Ints(value: List[Int]) extends AnyVal
|
|
|
|
case class Every(value: FiniteDuration) extends AnyVal
|
|
case class AtMost(value: FiniteDuration) extends AnyVal
|
|
|
|
case class Template(value: String) extends AnyVal
|