diff --git a/akka-http-core/src/main/scala/akka/http/model/Uri.scala b/akka-http-core/src/main/scala/akka/http/model/Uri.scala index db7d1ee2ad..6a3dd47c68 100644 --- a/akka-http-core/src/main/scala/akka/http/model/Uri.scala +++ b/akka-http-core/src/main/scala/akka/http/model/Uri.scala @@ -809,8 +809,9 @@ object UriRendering { renderPath(encode(r, head, charset, keep), tail, charset) } - def renderQuery[R <: Rendering](r: R, query: Query, charset: Charset): r.type = { - def enc(s: String): Unit = encode(r, s, charset, `strict-query-char-np`, replaceSpaces = true) + def renderQuery[R <: Rendering](r: R, query: Query, charset: Charset, + keep: CharPredicate = `strict-query-char-np`): r.type = { + def enc(s: String): Unit = encode(r, s, charset, keep, replaceSpaces = true) @tailrec def append(q: Query): r.type = q match { case Query.Empty ⇒ r diff --git a/akka-http-tests/src/test/scala/akka/http/FormDataSpec.scala b/akka-http-tests/src/test/scala/akka/http/FormDataSpec.scala new file mode 100644 index 0000000000..81b2efb471 --- /dev/null +++ b/akka-http-tests/src/test/scala/akka/http/FormDataSpec.scala @@ -0,0 +1,42 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +package akka.http + +import scala.concurrent.duration._ +import org.scalatest.{ BeforeAndAfterAll, Matchers, WordSpec } +import org.scalatest.concurrent.ScalaFutures +import akka.actor.ActorSystem +import akka.stream.FlowMaterializer +import akka.http.unmarshalling.Unmarshal +import akka.http.marshalling.Marshal +import akka.http.model._ + +class FormDataSpec extends WordSpec with Matchers with ScalaFutures with BeforeAndAfterAll { + implicit val system = ActorSystem(getClass.getSimpleName) + implicit val materializer = FlowMaterializer() + import system.dispatcher + + val formData = FormData(Map("surname" -> "Smith", "age" -> "42")) + + "The FormData infrastructure" should { + "properly round-trip the fields of www-urlencoded forms" in { + Marshal(formData).to[HttpEntity] + .flatMap(Unmarshal(_).to[FormData]).futureValue shouldEqual formData + } + + "properly marshal www-urlencoded forms containing special chars" in { + Marshal(FormData(Map("name" -> "Smith&Wesson"))).to[HttpEntity] + .flatMap(Unmarshal(_).to[String]).futureValue shouldEqual "name=Smith%26Wesson" + + Marshal(FormData(Map("name" -> "Smith+Wesson; hopefully!"))).to[HttpEntity] + .flatMap(Unmarshal(_).to[String]).futureValue shouldEqual "name=Smith%2BWesson%3B+hopefully%21" + } + } + + override def afterAll() = { + system.shutdown() + system.awaitTermination(10.seconds) + } +} diff --git a/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala b/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala index 0472ab7ee3..7b6849336d 100644 --- a/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala +++ b/akka-http/src/main/scala/akka/http/marshalling/PredefinedToEntityMarshallers.scala @@ -7,6 +7,7 @@ package akka.http.marshalling import java.nio.CharBuffer import scala.concurrent.ExecutionContext import scala.xml.NodeSeq +import akka.http.model.parser.CharacterClasses import akka.http.model.MediaTypes._ import akka.http.model._ import akka.http.util.{ FastFuture, StringRendering } @@ -57,7 +58,8 @@ trait PredefinedToEntityMarshallers extends MultipartMarshallers { implicit val FormDataMarshaller: ToEntityMarshaller[FormData] = Marshaller.withOpenCharset(`application/x-www-form-urlencoded`) { (formData, charset) ⇒ - val string = UriRendering.renderQuery(new StringRendering, Uri.Query(formData.fields: _*), charset.nioCharset).get + val query = Uri.Query(formData.fields: _*) + val string = UriRendering.renderQuery(new StringRendering, query, charset.nioCharset, CharacterClasses.unreserved).get HttpEntity(ContentType(`application/x-www-form-urlencoded`, charset), string) }