From f6d37190a4577c2a4d3b867fbd5372ac4eb71459 Mon Sep 17 00:00:00 2001 From: Adam Chlupacek Date: Sat, 3 Feb 2018 10:41:00 +0100 Subject: [PATCH 1/2] Updated uri scheme parsing according to spec. --- .../spinoco/protocol/http/HttpScheme.scala | 45 +++++++++++++------ .../scala/spinoco/protocol/http/Uri.scala | 4 +- .../spinoco/protocol/http/SchemeSpec.scala | 20 +++++++++ 3 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala diff --git a/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala b/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala index 8d2ce88..b2cb15d 100644 --- a/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala +++ b/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala @@ -1,27 +1,44 @@ package spinoco.protocol.http -import scodec.Codec +import scodec.{Codec, Err} import codec.helper._ -import spinoco.protocol.common.util._ +/** + * A generic protocol for Uri. + * + * @param tpe The type of the protocol. + */ +case class Scheme private[http](tpe: String) + +object Scheme { + + def parseScheme(toParse: String): Option[Scheme] = { + toParse.headOption.flatMap{ firstChar => + if (!firstChar.isLetter) None + else if (toParse.tail.forall(c => c.isLetterOrDigit || c == '+' || c == '-' || c == '.')){ + Some(new Scheme(toParse)) + } else None + } + } -object HttpScheme extends Enumeration { + val codec: Codec[Scheme] = { + import scodec.Attempt._ + trimmedAsciiToken.exmap( + s => fromOption(parseScheme(s), Err(s"The specified scheme is not valid according to RFC 3986: $s")) + , s => successful(s.tpe) + ) + } - val HTTP = Value("http") +} - val HTTPS = Value("https") +object HttpScheme { - val WS = Value("ws") + val HTTP = Scheme("http") - val WSS = Value("wss") + val HTTPS = Scheme("https") + val WS = Scheme("ws") - val codec: Codec[HttpScheme.Value] = { - import scodec.Attempt._ - trimmedAsciiToken.exmap( - s => attempt(HttpScheme.withName(s.toLowerCase)) - , s => successful(s.toString) - ) - } + val WSS = Scheme("wss") } diff --git a/http/src/main/scala/spinoco/protocol/http/Uri.scala b/http/src/main/scala/spinoco/protocol/http/Uri.scala index 888ba3b..313b7a0 100644 --- a/http/src/main/scala/spinoco/protocol/http/Uri.scala +++ b/http/src/main/scala/spinoco/protocol/http/Uri.scala @@ -17,7 +17,7 @@ import scala.annotation.tailrec * All values are decoded (no % escaping) */ sealed case class Uri( - scheme: HttpScheme.Value + scheme: Scheme , host: HostPort , path: Uri.Path , query: Uri.Query @@ -96,7 +96,7 @@ object Uri { ) } - (terminated(HttpScheme.codec, Terminator.constantString1("://")) ~ hostPathQueryCodec) + (terminated(Scheme.codec, Terminator.constantString1("://")) ~ hostPathQueryCodec) .xmap( { case (scheme, (host, path, query)) => Uri(scheme, host, path, query) } , uri => (uri.scheme, (uri.host, uri.path, uri.query)) diff --git a/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala b/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala new file mode 100644 index 0000000..0d5e727 --- /dev/null +++ b/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala @@ -0,0 +1,20 @@ +package spinoco.protocol.http + +import org.scalacheck.Properties +import org.scalacheck.Prop._ + +object SchemeSpec extends Properties("Schema"){ + + property("parse.valid") = protect{ + Scheme.parseScheme("http-1+3.two") ?= Some(Scheme("http-1+3.two")) + } + + property("parse.invalid.starts-digit") = protect{ + Scheme.parseScheme("1http-1+3.two") ?= None + } + + property("parse.invalid.non-valid-char") = protect{ + Scheme.parseScheme("http-1:3.two") ?= None + } + +} From 19d50d93444a047f2209bf273ffec24acd890d43 Mon Sep 17 00:00:00 2001 From: Adam Chlupacek Date: Sat, 3 Feb 2018 10:45:45 +0100 Subject: [PATCH 2/2] Now scheme is case insensitive. --- http/src/main/scala/spinoco/protocol/http/HttpScheme.scala | 6 +++--- http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala b/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala index b2cb15d..d61ca5c 100644 --- a/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala +++ b/http/src/main/scala/spinoco/protocol/http/HttpScheme.scala @@ -13,10 +13,10 @@ case class Scheme private[http](tpe: String) object Scheme { def parseScheme(toParse: String): Option[Scheme] = { - toParse.headOption.flatMap{ firstChar => + toParse.headOption.flatMap { firstChar => if (!firstChar.isLetter) None - else if (toParse.tail.forall(c => c.isLetterOrDigit || c == '+' || c == '-' || c == '.')){ - Some(new Scheme(toParse)) + else if (toParse.tail.forall(c => c.isLetterOrDigit || c == '+' || c == '-' || c == '.')) { + Some(new Scheme(toParse.toLowerCase)) } else None } } diff --git a/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala b/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala index 0d5e727..467783e 100644 --- a/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala +++ b/http/src/test/scala/spinoco/protocol/http/SchemeSpec.scala @@ -9,6 +9,10 @@ object SchemeSpec extends Properties("Schema"){ Scheme.parseScheme("http-1+3.two") ?= Some(Scheme("http-1+3.two")) } + property("parse.valid.upper-case") = protect{ + Scheme.parseScheme("http-1+3.TWO") ?= Some(Scheme("http-1+3.two")) + } + property("parse.invalid.starts-digit") = protect{ Scheme.parseScheme("1http-1+3.two") ?= None }