From d20f77918cf2b41f39d443ffb2121f920f4c4c49 Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Mon, 14 Sep 2020 14:49:25 +0200 Subject: [PATCH 01/15] Update play-file-watch to 1.1.13 (cherry picked from commit adccfba22523bc5f5000a3ad2a3ec76b066d81dd) --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 44ccdcd8983..6da0183f8fe 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -185,7 +185,7 @@ object Dependencies { ) } - val playFileWatch = "com.lightbend.play" %% "play-file-watch" % "1.1.12" + val playFileWatch = "com.lightbend.play" %% "play-file-watch" % "1.1.13" def runSupportDependencies(sbtVersion: String): Seq[ModuleID] = { (CrossVersion.binarySbtVersion(sbtVersion) match { From d896d2e8281c2e2403c0484f852f9e01a31759dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= Date: Wed, 30 Sep 2020 14:56:27 +0100 Subject: [PATCH 02/15] Update PlayReload.scala handle `Throwable` to avoid possible `MatchError` (cherry picked from commit 13dc52e9a9e8550fec2d662dec37de4f7f2e1d3c) --- .../src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala index f61bd35f4d4..e3efb251dea 100644 --- a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala +++ b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala @@ -39,7 +39,7 @@ object PlayReload { .find(_.severity == xsbti.Severity.Error) .map(CompilationException) .getOrElse(UnexpectedException(Some("The compilation failed without reporting any problem!"), Some(e))) - case e: Exception => UnexpectedException(unexpected = Some(e)) + case e => UnexpectedException(unexpected = Some(e)) } .getOrElse { UnexpectedException(Some("The compilation task failed without any exception!")) From 0096549cc1e8d310acdd1f8953d6779970f98706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= Date: Mon, 9 Nov 2020 15:46:52 +0000 Subject: [PATCH 03/15] Update dev-mode/sbt-plugin/src/main/scala/play/sbt/run/PlayReload.scala Co-authored-by: Renato Cavalcanti (cherry picked from commit 8c4656282a0d10b5394aea99b2ab033be063756e) --- .../src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala index e3efb251dea..44fb131822d 100644 --- a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala +++ b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala @@ -39,7 +39,7 @@ object PlayReload { .find(_.severity == xsbti.Severity.Error) .map(CompilationException) .getOrElse(UnexpectedException(Some("The compilation failed without reporting any problem!"), Some(e))) - case e => UnexpectedException(unexpected = Some(e)) + case NonFatal(e) => UnexpectedException(unexpected = Some(e)) } .getOrElse { UnexpectedException(Some("The compilation task failed without any exception!")) From a476623901c47aaa5143f329cc14e8b8503bc953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Ferreira?= Date: Tue, 10 Nov 2020 20:50:33 +0000 Subject: [PATCH 04/15] import (cherry picked from commit af346c9fb7f4d288c0a53c4b88399a02ab550a50) # Conflicts: # dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala --- .../src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala index 44fb131822d..1dee9f2862a 100644 --- a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala +++ b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala @@ -4,6 +4,13 @@ package play.sbt.run +<<<<<<< HEAD:dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala +======= +import java.util.Optional + +import scala.util.control.NonFatal + +>>>>>>> af346c9fb7... import:dev-mode/sbt-plugin/src/main/scala/play/sbt/run/PlayReload.scala import sbt._ import sbt.Keys._ import sbt.internal.Output From 992f2deacd6d98f495655f59ea063ce331da2be3 Mon Sep 17 00:00:00 2001 From: Renato Cavalcanti Date: Mon, 16 Nov 2020 14:26:44 +0100 Subject: [PATCH 05/15] fixed merge conflict --- .../src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala index 1dee9f2862a..3c95502e677 100644 --- a/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala +++ b/dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala @@ -4,13 +4,8 @@ package play.sbt.run -<<<<<<< HEAD:dev-mode/sbt-plugin/src/main/scala-sbt-1.0/play/sbt/run/PlayReload.scala -======= -import java.util.Optional - import scala.util.control.NonFatal ->>>>>>> af346c9fb7... import:dev-mode/sbt-plugin/src/main/scala/play/sbt/run/PlayReload.scala import sbt._ import sbt.Keys._ import sbt.internal.Output From 3d752367420ab2f7d59f285c63b808b8b7d98145 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Thu, 19 Nov 2020 12:53:02 +0100 Subject: [PATCH 06/15] Scala 2.13.4 (cherry picked from commit a9ea5f074efdfb09188f836e079f6975c552b93c) --- documentation/build.sbt | 4 ++-- documentation/manual/hacking/BuildingFromSource.md | 2 +- documentation/manual/releases/release27/Highlights27.md | 2 +- .../manual/releases/release28/migration28/Migration28.md | 4 ++-- .../manual/working/commonGuide/build/sbtDependencies.md | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/documentation/build.sbt b/documentation/build.sbt index 2e9a1481b13..f6ecde012b5 100644 --- a/documentation/build.sbt +++ b/documentation/build.sbt @@ -77,8 +77,8 @@ lazy val main = Project("Play-Documentation", file(".")) unmanagedResourceDirectories in Test ++= (baseDirectory.value / "manual" / "detailedTopics" ** "code").get, // Don't include sbt files in the resources excludeFilter in (Test, unmanagedResources) := (excludeFilter in (Test, unmanagedResources)).value || "*.sbt", - crossScalaVersions := Seq("2.13.2", "2.12.11"), - scalaVersion := "2.13.2", + crossScalaVersions := Seq("2.13.4", "2.12.11"), + scalaVersion := "2.13.4", fork in Test := true, javaOptions in Test ++= Seq("-Xmx512m", "-Xms128m"), headerLicense := Some(HeaderLicense.Custom("Copyright (C) Lightbend Inc. ")), diff --git a/documentation/manual/hacking/BuildingFromSource.md b/documentation/manual/hacking/BuildingFromSource.md index e8baab04bb3..e2fd683f30c 100644 --- a/documentation/manual/hacking/BuildingFromSource.md +++ b/documentation/manual/hacking/BuildingFromSource.md @@ -38,7 +38,7 @@ This will build and publish Play for the default Scala version. If you want to p Or to publish for a specific Scala version: ```bash -> ++ 2.13.2 publishLocal +> ++ 2.13.4 publishLocal ``` ## Build the documentation diff --git a/documentation/manual/releases/release27/Highlights27.md b/documentation/manual/releases/release27/Highlights27.md index 3601687c36b..6cd6f49cd05 100644 --- a/documentation/manual/releases/release27/Highlights27.md +++ b/documentation/manual/releases/release27/Highlights27.md @@ -24,7 +24,7 @@ scalaVersion := "2.11.12" For Scala 2.13: ```scala -scalaVersion := "2.13.2" +scalaVersion := "2.13.4" ``` ## Lifecycle managed by Akka's Coordinated Shutdown diff --git a/documentation/manual/releases/release28/migration28/Migration28.md b/documentation/manual/releases/release28/migration28/Migration28.md index f9c48f673f7..4d8590f2ac1 100644 --- a/documentation/manual/releases/release28/migration28/Migration28.md +++ b/documentation/manual/releases/release28/migration28/Migration28.md @@ -47,14 +47,14 @@ Play 2.8 support Scala 2.12 and 2.13, but not 2.11, which has reached its end of To set the Scala version in sbt, simply set the `scalaVersion` key, for example: ```scala -scalaVersion := "2.13.2" +scalaVersion := "2.13.4" ``` If you have a single project build, then this setting can just be placed on its own line in `build.sbt`. However, if you have a multi-project build, then the scala version setting must be set on each project. Typically, in a multi-project build, you will have some common settings shared by every project, this is the best place to put the setting, for example: ```scala def commonSettings = Seq( - scalaVersion := "2.13.2" + scalaVersion := "2.13.4" ) val projectA = (project in file("projectA")) diff --git a/documentation/manual/working/commonGuide/build/sbtDependencies.md b/documentation/manual/working/commonGuide/build/sbtDependencies.md index 6a77edcc86c..e132af71e68 100644 --- a/documentation/manual/working/commonGuide/build/sbtDependencies.md +++ b/documentation/manual/working/commonGuide/build/sbtDependencies.md @@ -37,7 +37,7 @@ If you use `groupID %% artifactID % revision` rather than `groupID % artifactID @[explicit-scala-version-dep](code/dependencies.sbt) -Assuming the `scalaVersion` for your build is `2.13.2`, the following is identical (note the double `%%` after `"org.scala-tools"`): +Assuming the `scalaVersion` for your build is `2.13.4`, the following is identical (note the double `%%` after `"org.scala-tools"`): @[auto-scala-version-dep](code/dependencies.sbt) From 11708ff966646ff9d2defe67971c14d51a9ebefa Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Mon, 23 Nov 2020 15:27:23 +0100 Subject: [PATCH 07/15] BOM for Play Co-authored-By: Enno Runne <458526+ennru@users.noreply.github.com> --- build.sbt | 34 ++++++++++++++++++++++++---------- project/plugins.sbt | 19 ++++++++++--------- 2 files changed, 34 insertions(+), 19 deletions(-) diff --git a/build.sbt b/build.sbt index a81ff0d8d24..54306771d9d 100644 --- a/build.sbt +++ b/build.sbt @@ -58,6 +58,17 @@ lazy val StreamsProject = PlayCrossBuiltProject("Play-Streams", "core/play-strea lazy val PlayExceptionsProject = PlayNonCrossBuiltProject("Play-Exceptions", "core/play-exceptions") +lazy val billOfMaterials = PlayCrossBuiltProject("bill-of-materials", "dev-mode/bill-of-materials") + .enablePlugins(BillOfMaterialsPlugin) + .disablePlugins(MimaPlugin) + .settings( + name := "play-bom", + bomIncludeProjects := userProjects, + pomExtra := pomExtra.value :+ bomDependenciesListing.value, + publishTo := sonatypePublishToBundle.value, + mimaPreviousArtifacts := Set.empty + ) + lazy val PlayJodaFormsProject = PlayCrossBuiltProject("Play-Joda-Forms", "web/play-joda-forms") .settings( libraryDependencies ++= joda @@ -439,12 +450,11 @@ lazy val PlayDocsSbtPlugin = PlaySbtPluginProject("Play-Docs-Sbt-Plugin", "dev-m // https://www.scala-sbt.org/1.x/docs/Multi-Project.html#Aggregation // // Keep in mind that specific configurations (like skip in publish) will be respected. -lazy val aggregatedProjects = Seq[ProjectReference]( +lazy val userProjects = Seq[ProjectReference]( PlayProject, PlayGuiceProject, BuildLinkProject, RoutesCompilerProject, - SbtRoutesCompilerProject, PlayAkkaHttpServerProject, PlayAkkaHttp2SupportProject, PlayCacheProject, @@ -460,26 +470,30 @@ lazy val aggregatedProjects = Seq[ProjectReference]( PlayJavaJdbcProject, PlayJpaProject, PlayNettyServerProject, - PlayMicrobenchmarkProject, PlayServerProject, PlayLogback, PlayWsProject, PlayAhcWsProject, PlayOpenIdProject, - RunSupportProject, - SbtPluginProject, - SbtScriptedToolsProject, PlaySpecs2Project, PlayTestProject, PlayExceptionsProject, - PlayDocsProject, PlayFiltersHelpersProject, - PlayIntegrationTestProject, - PlayDocsSbtPlugin, StreamsProject, PlayClusterSharding, PlayJavaClusterSharding ) +lazy val nonUserProjects = Seq[ProjectReference]( + PlayMicrobenchmarkProject, + PlayDocsProject, + PlayIntegrationTestProject, + PlayDocsSbtPlugin, + RunSupportProject, + SbtRoutesCompilerProject, + SbtPluginProject, + SbtScriptedToolsProject, + billOfMaterials +) lazy val PlayFramework = Project("Play-Framework", file(".")) .enablePlugins(PlayRootProject) @@ -496,6 +510,6 @@ lazy val PlayFramework = Project("Play-Framework", file(".")) commands += Commands.quickPublish, Release.settings ) - .aggregate(aggregatedProjects: _*) + .aggregate((userProjects ++ nonUserProjects): _*) addCommandAlias("javafmtAll", ";javafmt; test:javafmt; it:javafmt") diff --git a/project/plugins.sbt b/project/plugins.sbt index fdebf7fc1c3..59ec617867c 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -25,15 +25,16 @@ logLevel := Level.Warn scalacOptions ++= Seq("-deprecation", "-language:_") -addSbtPlugin("com.typesafe.play" % "interplay" % interplay) -addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % sbtTwirl) -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % mima) -addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % sbtJavaAgent) -addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % sbtJavaFormatter) -addSbtPlugin("pl.project13.scala" % "sbt-jmh" % sbtJmh) -addSbtPlugin("de.heikoseeberger" % "sbt-header" % sbtHeader) -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % scalafmt) -addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.0.0") +addSbtPlugin("com.typesafe.play" % "interplay" % interplay) +addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % sbtTwirl) +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % mima) +addSbtPlugin("com.lightbend.sbt" % "sbt-bill-of-materials" % "1.0.1") +addSbtPlugin("com.lightbend.sbt" % "sbt-javaagent" % sbtJavaAgent) +addSbtPlugin("com.lightbend.sbt" % "sbt-java-formatter" % sbtJavaFormatter) +addSbtPlugin("pl.project13.scala" % "sbt-jmh" % sbtJmh) +addSbtPlugin("de.heikoseeberger" % "sbt-header" % sbtHeader) +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % scalafmt) +addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.0.0") libraryDependencies ++= Seq( "org.webjars" % "webjars-locator-core" % webjarsLocatorCore From 32e878ccabdbd7d7baacafed1778ed8d0afe8ffd Mon Sep 17 00:00:00 2001 From: Scala Steward Date: Wed, 2 Dec 2020 06:41:09 +0100 Subject: [PATCH 08/15] Update jackson-databind to 2.10.5.1 (cherry picked from commit b804c8bed1efb2bcbf958373cff50a6468fd8ff6) # Conflicts: # project/Dependencies.scala --- project/Dependencies.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index bbb27255bd4..e78fa296eb7 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -30,6 +30,7 @@ object Dependencies { "org.scalacheck" %% "scalacheck" % "1.14.3" % Test ) +<<<<<<< HEAD // We need to use an older version of specs2 for sbt // because we need Scala 2.10 support (sbt 0.13). val specs2VersionForSbt = "3.10.0" @@ -38,6 +39,10 @@ object Dependencies { val jacksonVersion = "2.10.4" val jacksonDatabindVersion = "2.10.4" +======= + val jacksonVersion = "2.10.5" + val jacksonDatabindVersion = "2.10.5.1" +>>>>>>> b804c8bed1... Update jackson-databind to 2.10.5.1 val jacksonDatabind = Seq("com.fasterxml.jackson.core" % "jackson-databind" % jacksonDatabindVersion) val jacksons = Seq( "com.fasterxml.jackson.core" % "jackson-core", From 7087a5bb4c4e312063768129037d249e736ecddb Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Wed, 2 Dec 2020 21:11:07 +0100 Subject: [PATCH 09/15] Fix conflicts --- project/Dependencies.scala | 5 ----- 1 file changed, 5 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index e78fa296eb7..25593e51a59 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -30,19 +30,14 @@ object Dependencies { "org.scalacheck" %% "scalacheck" % "1.14.3" % Test ) -<<<<<<< HEAD // We need to use an older version of specs2 for sbt // because we need Scala 2.10 support (sbt 0.13). val specs2VersionForSbt = "3.10.0" val specs2DepsForSbt = specs2Deps.map(_.withRevision(specs2VersionForSbt)) val specsMatcherExtraForSbt = specsMatcherExtra.withRevision(specs2VersionForSbt) - val jacksonVersion = "2.10.4" - val jacksonDatabindVersion = "2.10.4" -======= val jacksonVersion = "2.10.5" val jacksonDatabindVersion = "2.10.5.1" ->>>>>>> b804c8bed1... Update jackson-databind to 2.10.5.1 val jacksonDatabind = Seq("com.fasterxml.jackson.core" % "jackson-databind" % jacksonDatabindVersion) val jacksons = Seq( "com.fasterxml.jackson.core" % "jackson-core", From 7b3348892d30cb6369bae77afc3844bd66756269 Mon Sep 17 00:00:00 2001 From: Matthias Kurz Date: Wed, 2 Dec 2020 21:29:01 +0100 Subject: [PATCH 10/15] Upgrade akka and akka-http (cherry picked from commit d204856027fe81f5f65cf8698649b019fdc2d277) --- project/Dependencies.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 25593e51a59..9790b8f81f0 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -7,8 +7,8 @@ import Keys._ import buildinfo.BuildInfo object Dependencies { - val akkaVersion: String = sys.props.getOrElse("akka.version", "2.6.8") - val akkaHttpVersion = "10.1.12" + val akkaVersion: String = sys.props.getOrElse("akka.version", "2.6.10") + val akkaHttpVersion = "10.1.13" val sslConfig = "com.typesafe" %% "ssl-config-core" % "0.4.2" From cebb701e97af566078d592615b5fe01415e100e3 Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Mon, 16 Nov 2020 14:31:24 +0100 Subject: [PATCH 11/15] Form parsing honors maxMemoryBuffer (bp #10543) Co-authored-by: Renato Cavalcanti Co-authored-by: Will Sargent --- .../scala/play/it/action/FormActionSpec.scala | 2 + .../src/main/scala/play/api/data/Form.scala | 64 +++++++++++++------ .../main/scala/play/api/mvc/BodyParsers.scala | 14 +++- .../test/scala/play/api/data/FormSpec.scala | 2 + .../scala/play/api/data/FormUtilsSpec.scala | 17 +++++ .../release27/migration27/Migration27.md | 43 +++++++++++++ .../main/forms/code/ScalaForms.scala | 1 + .../code/specs2/ExampleControllerSpec.scala | 1 + .../code/specs2/ExampleMessagesSpec.scala | 1 + .../main/ws/code/ScalaOpenIdSpec.scala | 2 + project/BuildSettings.scala | 8 +++ .../src/main/java/play/data/DynamicForm.java | 6 +- .../src/main/java/play/data/Form.java | 13 ++-- .../scala/play/data/DynamicFormSpec.scala | 31 ++++++++- 14 files changed, 173 insertions(+), 32 deletions(-) diff --git a/core/play-integration-test/src/it/scala/play/it/action/FormActionSpec.scala b/core/play-integration-test/src/it/scala/play/it/action/FormActionSpec.scala index 5d517b6529b..c6b024223f7 100644 --- a/core/play-integration-test/src/it/scala/play/it/action/FormActionSpec.scala +++ b/core/play-integration-test/src/it/scala/play/it/action/FormActionSpec.scala @@ -20,6 +20,8 @@ import play.api.test.WsTestClient import play.api.routing.Router class FormActionSpec extends PlaySpecification with WsTestClient { + import FormBinding.Implicits._ + case class User( name: String, email: String, diff --git a/core/play/src/main/scala/play/api/data/Form.scala b/core/play/src/main/scala/play/api/data/Form.scala index 7c72fc8dea7..d80143c18eb 100644 --- a/core/play/src/main/scala/play/api/data/Form.scala +++ b/core/play/src/main/scala/play/api/data/Form.scala @@ -107,25 +107,8 @@ case class Form[T](mapping: Mapping[T], data: Map[String, String], errors: Seq[F * * @return a copy of this form filled with the new data */ - def bindFromRequest()(implicit request: play.api.mvc.Request[_]): Form[T] = { - import play.api.mvc.MultipartFormData - val unwrap = request.body match { - case body: play.api.mvc.AnyContent => - body.asFormUrlEncoded.orElse(body.asMultipartFormData).orElse(body.asJson).getOrElse(body) - case body => body - } - val data: Map[String, Seq[String]] = unwrap match { - case body: Map[_, _] => body.asInstanceOf[Map[String, Seq[String]]] - case body: MultipartFormData[_] => body.asFormUrlEncoded - case Right(body: MultipartFormData[_]) => body.asFormUrlEncoded - case body: play.api.libs.json.JsValue => FormUtils.fromJson(body, Form.FromJsonMaxChars).mapValues(Seq(_)).toMap - case _ => Map.empty - } - val method: Map[_ <: String, Seq[String]] = request.method.toUpperCase match { - case HttpVerbs.POST | HttpVerbs.PUT | HttpVerbs.PATCH => Map.empty - case _ => request.queryString - } - bindFromRequest((data ++ method).toMap) + def bindFromRequest()(implicit request: play.api.mvc.Request[_], formBinding: FormBinding): Form[T] = { + bindFromRequest(formBinding(request)) } def bindFromRequest(data: Map[String, Seq[String]]): Form[T] = { @@ -1069,3 +1052,46 @@ trait ObjectMapping { case class FormJsonExpansionTooLarge(limit: Long) extends RuntimeException(s"Binding form from JSON exceeds form expansion limit of $limit") with NoStackTrace + +trait FormBinding { + def apply(request: play.api.mvc.Request[_]): Map[String, Seq[String]] +} + +object FormBinding { + object Implicits { + + /** + * Convenience implicit for testing and other scenarios where it's not important + * to verify payload limits (e.g. prevent OutOfMemoryError's). + * + * Prefer using a FormBinding provided by PlayBodyParsers#formBinding since that honours play.http.parser.maxMemoryBuffer limits. + */ + implicit val formBinding: FormBinding = new DefaultFormBinding(Form.FromJsonMaxChars) + } +} + +class DefaultFormBinding(maxChars: Long) extends FormBinding { + def apply(request: play.api.mvc.Request[_]): Map[String, Seq[String]] = { + import play.api.mvc.MultipartFormData + val unwrap = request.body match { + case body: play.api.mvc.AnyContent => + body.asFormUrlEncoded.orElse(body.asMultipartFormData).orElse(body.asJson).getOrElse(body) + case body => body + } + val data: Map[String, Seq[String]] = unwrap match { + case body: Map[_, _] => body.asInstanceOf[Map[String, Seq[String]]] + case body: MultipartFormData[_] => multipartFormParse(body) + case Right(body: MultipartFormData[_]) => multipartFormParse(body) + case body: play.api.libs.json.JsValue => jsonParse(body).toMap + case _ => Map.empty + } + val method: Map[_ <: String, Seq[String]] = request.method.toUpperCase match { + case HttpVerbs.POST | HttpVerbs.PUT | HttpVerbs.PATCH => Map.empty + case _ => request.queryString + } + (data ++ method).toMap + } + private def multipartFormParse(body: MultipartFormData[_]) = body.asFormUrlEncoded + + private def jsonParse(jsValue: JsValue) = FormUtils.fromJson(jsValue, maxChars).mapValues(Seq(_)) +} diff --git a/core/play/src/main/scala/play/api/mvc/BodyParsers.scala b/core/play/src/main/scala/play/api/mvc/BodyParsers.scala index 113ecfaeafe..87b03dc8401 100644 --- a/core/play/src/main/scala/play/api/mvc/BodyParsers.scala +++ b/core/play/src/main/scala/play/api/mvc/BodyParsers.scala @@ -19,7 +19,9 @@ import akka.stream.scaladsl.StreamConverters import akka.stream.stage._ import akka.util.ByteString import play.api._ +import play.api.data.DefaultFormBinding import play.api.data.Form +import play.api.data.FormBinding import play.api.http.Status._ import play.api.http._ import play.api.libs.Files.SingletonTemporaryFileCreator @@ -435,7 +437,7 @@ trait PlayBodyParsers extends BodyParserUtils { * You can configure it in application.conf: * * {{{ - * play.http.parser.maxMemoryBuffer = 512k + * play.http.parser.maxMemoryBuffer = 100k * }}} */ def DefaultMaxTextLength: Long = config.maxMemoryBuffer @@ -456,6 +458,11 @@ trait PlayBodyParsers extends BodyParserUtils { */ def DefaultAllowEmptyFileUploads: Boolean = false + // -- General purpose + + def formBinding(maxChars: Long = DefaultMaxTextLength): FormBinding = new DefaultFormBinding(maxChars) + implicit val defaultFormBinding: FormBinding = formBinding(DefaultMaxTextLength) + // -- Text parser /** @@ -720,11 +727,12 @@ trait PlayBodyParsers extends BodyParserUtils { onErrors: Form[A] => Result = (_: Form[A]) => Results.BadRequest ): BodyParser[A] = BodyParser { requestHeader => - val parser = anyContent(maxLength) + val parser = anyContent(maxLength) + val binding = formBinding(maxLength.getOrElse(DefaultMaxTextLength)) parser(requestHeader).map { resultOrBody => resultOrBody.right.flatMap { body => form - .bindFromRequest()(Request[AnyContent](requestHeader, body)) + .bindFromRequest()(Request[AnyContent](requestHeader, body), binding) .fold(formErrors => Left(onErrors(formErrors)), a => Right(a)) } }(Execution.trampoline) diff --git a/core/play/src/test/scala/play/api/data/FormSpec.scala b/core/play/src/test/scala/play/api/data/FormSpec.scala index ccd237a0ae2..9c339587216 100644 --- a/core/play/src/test/scala/play/api/data/FormSpec.scala +++ b/core/play/src/test/scala/play/api/data/FormSpec.scala @@ -18,6 +18,8 @@ import play.api.mvc.MultipartFormData import play.core.test.FakeRequest class FormSpec extends Specification { + import FormBinding.Implicits.formBinding + "A form" should { "have an error due to a malformed email" in { val f5 = ScalaForms.emailForm.fillAndValidate(("john@", "John")) diff --git a/core/play/src/test/scala/play/api/data/FormUtilsSpec.scala b/core/play/src/test/scala/play/api/data/FormUtilsSpec.scala index 622c50788a7..127582e17d1 100644 --- a/core/play/src/test/scala/play/api/data/FormUtilsSpec.scala +++ b/core/play/src/test/scala/play/api/data/FormUtilsSpec.scala @@ -97,6 +97,23 @@ class FormUtilsSpec extends Specification { }) must throwA[FormJsonExpansionTooLarge] } + "abort parsing when maximum memory is used" in { + // Even when the JSON is small, if the memory limit is exceed the parsing must stop. + val keyLength = 10 + val itemCount = 10 + val maxChars = 3 // yeah, maxChars is only 3 chars. We want to hit the limit. + (try { + val jsString = Json.parse(s""" "${"a" * keyLength}" """) + FormUtils.fromJson( + jsString, + maxChars + ) + } catch { + case _: OutOfMemoryError => + ko("OutOfMemoryError") + }) must throwA[FormJsonExpansionTooLarge] + } + "not run out of heap when converting arrays with very long keys" in { // a similar scenario to the previous one but this would cause OOME if it weren't for the limit val keyLength = 10000 diff --git a/documentation/manual/releases/release27/migration27/Migration27.md b/documentation/manual/releases/release27/migration27/Migration27.md index fe3417d3f2a..3732a2a7c9f 100644 --- a/documentation/manual/releases/release27/migration27/Migration27.md +++ b/documentation/manual/releases/release27/migration27/Migration27.md @@ -100,6 +100,49 @@ Other methods that were added to improve Java API: The API for body parser was mixing `Integer` and `Long` to define buffer lengths which could lead to overflow of values. The configuration is now uniformed to use `Long`. It means that if you are depending on `play.api.mvc.PlayBodyParsers.DefaultMaxTextLength` for example, you then need to use a `Long`. As such, `play.api.http.ParserConfiguration.maxMemoryBuffer` is now a `Long` too. +### Parser `maxMemoryBuffer` limits + +Some payloads expand in memory when being parsed. So, the memory representation takes more space than the plaintext representation read from the wire. JSON is one of these formats. In order to prevent attacks that could lead to out of memory errors causing Denial-of-Service, body parsing and form binding must honour the `play.http.parser.maxMemoruBuffer`. + +The value of `play.http.parser.maxMemoruBuffer` is honored ouf of the box. If you are a user of the Play Scala API, you will need to declare a new implicit value providing the apprpriate instance of a `FormBinding`: + +_Before_ + +```scala + val formValidationResult = form.bindFromRequest +``` + +_After_ + +```scala +// Assuming you have: +class MyController @Inject()(cc: MessagesControllerComponents) { + implicit val fb = cc.parsers.defaultFormBinding + val formValidationResult = form.bindFromRequest + ... +} +``` + +You can also use a form binding with a customized limit using: + +```scala +// Assuming you have: +class MyController @Inject()(cc: MessagesControllerComponents) { + implicit val fb = cc.parsers.formBinding(300*1024) // limit to 300KiB + val formValidationResult = form.bindFromRequest + ... +} +``` + +Finally, in tests, you probably don't need to read the value from `Config` and a default, hardcoded value is fine. In that case you can simply: + +````scala +import play.api.data.FormBinding.Implicits._ +``` + +The `FormBinding.Implicits._` implicits can be used from production code but that is discouraged since they use hardcoded values that don't honor the `play.http.parser.maxMemoruBuffer` configuration. + + ### New fields and methods added to `FilePart` and `FileInfo` [`Scala's`](api/scala/play/api/mvc/MultipartFormData$$FilePart.html) and [`Java's`](api/java/play/mvc/Http.MultipartFormData.FilePart.html) `FilePart` classes have two new fields/methods which provide you the file size and the disposition type of a file that was uploaded via the `multipart/form-data` encoding: diff --git a/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala b/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala index 8fd56733da3..654f3cfffef 100644 --- a/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala +++ b/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala @@ -31,6 +31,7 @@ package scalaguide.forms.scalaforms { // #validation-imports import play.api.data.validation.Constraints._ // #validation-imports + import play.api.data.FormBinding.Implicits._ @RunWith(classOf[JUnitRunner]) class ScalaFormsSpec extends Specification with ControllerHelpers { diff --git a/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleControllerSpec.scala b/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleControllerSpec.scala index abcace2c342..fa4fe415ce0 100644 --- a/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleControllerSpec.scala +++ b/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleControllerSpec.scala @@ -12,6 +12,7 @@ import play.api.mvc._ import play.api.test._ import scala.concurrent.Future +import play.api.data.FormBinding.Implicits._ class ExampleControllerSpec extends PlaySpecification with Results { "Example Page#index" should { diff --git a/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleMessagesSpec.scala b/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleMessagesSpec.scala index a27b1ed6b5b..3f59d610266 100644 --- a/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleMessagesSpec.scala +++ b/documentation/manual/working/scalaGuide/main/tests/code/specs2/ExampleMessagesSpec.scala @@ -16,6 +16,7 @@ class ExampleMessagesSpec extends PlaySpecification with ControllerHelpers { import play.api.data.Forms._ import play.api.data.Form import play.api.i18n._ + import play.api.data.FormBinding.Implicits._ case class UserData(name: String, age: Int) diff --git a/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala b/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala index b53659cfc20..1da912bb6fd 100644 --- a/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala +++ b/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala @@ -24,6 +24,8 @@ class IdController @Inject() (val openIdClient: OpenIdClient, c: ControllerCompo //#dependency class ScalaOpenIdSpec extends PlaySpecification { + import play.api.data.FormBinding.Implicits._ + "Scala OpenId" should { "be injectable" in new WithApplication() with Injecting { val controller = diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index 4a1e34ea1f3..e9752462300 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -215,6 +215,14 @@ object BuildSettings { // Remove deprecated FakeKeyStore ProblemFilters.exclude[MissingClassProblem]("play.core.server.ssl.FakeKeyStore$"), ProblemFilters.exclude[MissingClassProblem]("play.core.server.ssl.FakeKeyStore"), + // Honour maxMemoryBuffer when binding Json to form + ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.data.Form.bindFromRequest"), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "play.api.mvc.PlayBodyParsers.play$api$mvc$PlayBodyParsers$_setter_$defaultFormBinding_=" + ), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.defaultFormBinding"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding$default$1"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding"), ), unmanagedSourceDirectories in Compile += { val suffix = CrossVersion.partialVersion(scalaVersion.value) match { diff --git a/web/play-java-forms/src/main/java/play/data/DynamicForm.java b/web/play-java-forms/src/main/java/play/data/DynamicForm.java index 5cdbc039147..7a295654f97 100644 --- a/web/play-java-forms/src/main/java/play/data/DynamicForm.java +++ b/web/play-java-forms/src/main/java/play/data/DynamicForm.java @@ -279,9 +279,9 @@ public DynamicForm bindFromRequestData( @Deprecated public DynamicForm bind(Lang lang, TypedMap attrs, JsonNode data, String... allowedFields) { logger.warn( - "Binding json field from form with a hardcoded max size of {} bytes. This is deprecated. Use bind(Lang, TypedMap, JsonNode, Int, String...) instead.", - FROM_JSON_MAX_CHARS); - return bind(lang, attrs, data, FROM_JSON_MAX_CHARS, allowedFields); + "Binding json field from form with a hardcoded max size of {} bytes. This is deprecated. Use bind(Lang, TypedMap, JsonNode, Long, String...) instead.", + maxJsonChars()); + return bind(lang, attrs, data, maxJsonChars(), allowedFields); } @Override diff --git a/web/play-java-forms/src/main/java/play/data/Form.java b/web/play-java-forms/src/main/java/play/data/Form.java index f906d5f5226..89702889af3 100644 --- a/web/play-java-forms/src/main/java/play/data/Form.java +++ b/web/play-java-forms/src/main/java/play/data/Form.java @@ -90,8 +90,6 @@ public class Form { private static final String INVALID_MSG_KEY = "error.invalid"; - protected static final long FROM_JSON_MAX_CHARS = Form$.MODULE$.FromJsonMaxChars(); - /** Defines a form element's display name. */ @Retention(RUNTIME) @Target({ANNOTATION_TYPE}) @@ -574,6 +572,10 @@ public Form( this.directFieldAccess = directFieldAccess; } + protected long maxJsonChars() { + return config.getMemorySize("play.http.parser.maxMemoryBuffer").toBytes(); + } + protected Map requestData(Http.Request request) { Map urlFormEncoded = new HashMap<>(); @@ -587,13 +589,12 @@ protected Map requestData(Http.Request request) { } Map jsonData = new HashMap<>(); - long maxMemoryBuffer = config.getMemorySize("play.http.parser.maxMemoryBuffer").toBytes(); if (request.body().asJson() != null) { jsonData = play.libs.Scala.asJava( play.api.data.FormUtils.fromJson( play.api.libs.json.Json.parse(play.libs.Json.stringify(request.body().asJson())), - maxMemoryBuffer)); + maxJsonChars())); } Map data = new HashMap<>(); @@ -737,8 +738,8 @@ public Form bindFromRequestData( public Form bind(Lang lang, TypedMap attrs, JsonNode data, String... allowedFields) { logger.warn( "Binding json field from form with a hardcoded max size of {} bytes. This is deprecated. Use bind(Lang, TypedMap, JsonNode, Int, String...) instead.", - FROM_JSON_MAX_CHARS); - return bind(lang, attrs, data, FROM_JSON_MAX_CHARS, allowedFields); + maxJsonChars()); + return bind(lang, attrs, data, maxJsonChars(), allowedFields); } /** diff --git a/web/play-java-forms/src/test/scala/play/data/DynamicFormSpec.scala b/web/play-java-forms/src/test/scala/play/data/DynamicFormSpec.scala index 69e1e958d6c..431075dae09 100644 --- a/web/play-java-forms/src/test/scala/play/data/DynamicFormSpec.scala +++ b/web/play-java-forms/src/test/scala/play/data/DynamicFormSpec.scala @@ -7,15 +7,24 @@ package play.data import com.typesafe.config.ConfigFactory import java.nio.file.Files +import akka.util.ByteString +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.node.JsonNodeFactory +import com.fasterxml.jackson.databind.node.ObjectNode +import com.fasterxml.jackson.databind.node.TextNode import javax.validation.Validation - import org.specs2.mutable.Specification +import play.api.data.FormJsonExpansionTooLarge import play.api.i18n.DefaultMessagesApi import play.core.j.PlayFormsMagicForJava.javaFieldtoScalaField import play.data.format.Formatters import play.libs.Files.SingletonTemporaryFileCreator import play.libs.Files.TemporaryFile +import play.mvc.BodyParser.Json +import play.mvc.Http.Headers import play.mvc.Http.MultipartFormData.FilePart +import play.mvc.Http.RequestBody +import play.mvc.Http.RequestBuilder import views.html.helper.FieldConstructor.defaultField import views.html.helper.inputText @@ -195,5 +204,25 @@ class DynamicFormSpec extends CommonFormSpec { sField.constraints must_== Nil sField.errors must_== Nil } + + "fail with exception when the json paylod is bigger than default maxBufferSize" in { + val cfg = ConfigFactory.parseString(""" + |play.http.parser.maxMemoryBuffer = 32 + |""".stripMargin).withFallback(config) + val form = new DynamicForm(jMessagesApi, new Formatters(jMessagesApi), validatorFactory, cfg) + val longString = + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + val textNode: JsonNode = new TextNode(longString) + val req = new RequestBuilder() + .method("POST") + .uri("http://localhost/test") + .header("Content-type", "application/json") + .bodyJson(textNode) + .build() + + form.bindFromRequest(req) must throwA[FormJsonExpansionTooLarge].like { + case e => e.getMessage must equalTo("Binding form from JSON exceeds form expansion limit of 32") + } + } } } From a715d00c4f3689a0404e6364f398d6665feaba0c Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Wed, 2 Dec 2020 12:06:21 +0100 Subject: [PATCH 12/15] Provide a formBinding out of the box (bp #10565) --- .../src/main/scala/play/api/data/Form.scala | 2 +- .../main/scala/play/api/mvc/BodyParsers.scala | 1 - .../main/scala/play/api/mvc/Controller.scala | 3 ++ .../release27/migration27/Migration27.md | 33 ++++--------------- .../main/forms/code/ScalaForms.scala | 4 ++- .../main/ws/code/ScalaOpenIdSpec.scala | 2 -- project/BuildSettings.scala | 6 ++++ 7 files changed, 19 insertions(+), 32 deletions(-) diff --git a/core/play/src/main/scala/play/api/data/Form.scala b/core/play/src/main/scala/play/api/data/Form.scala index d80143c18eb..7b16530a99d 100644 --- a/core/play/src/main/scala/play/api/data/Form.scala +++ b/core/play/src/main/scala/play/api/data/Form.scala @@ -100,7 +100,7 @@ case class Form[T](mapping: Mapping[T], data: Map[String, String], errors: Seq[F * of the JSON. `parse.DefaultMaxTextLength` is recommended to passed for this parameter. * @return a copy of this form, filled with the new data */ - def bind(data: JsValue, maxChars: Int): Form[T] = bind(FormUtils.fromJson(data, maxChars)) + def bind(data: JsValue, maxChars: Long): Form[T] = bind(FormUtils.fromJson(data, maxChars)) /** * Binds request data to this form, i.e. handles form submission. diff --git a/core/play/src/main/scala/play/api/mvc/BodyParsers.scala b/core/play/src/main/scala/play/api/mvc/BodyParsers.scala index 87b03dc8401..9c11ea4b61c 100644 --- a/core/play/src/main/scala/play/api/mvc/BodyParsers.scala +++ b/core/play/src/main/scala/play/api/mvc/BodyParsers.scala @@ -461,7 +461,6 @@ trait PlayBodyParsers extends BodyParserUtils { // -- General purpose def formBinding(maxChars: Long = DefaultMaxTextLength): FormBinding = new DefaultFormBinding(maxChars) - implicit val defaultFormBinding: FormBinding = formBinding(DefaultMaxTextLength) // -- Text parser diff --git a/core/play/src/main/scala/play/api/mvc/Controller.scala b/core/play/src/main/scala/play/api/mvc/Controller.scala index 555f431110b..378b4819ab9 100644 --- a/core/play/src/main/scala/play/api/mvc/Controller.scala +++ b/core/play/src/main/scala/play/api/mvc/Controller.scala @@ -5,6 +5,7 @@ package play.api.mvc import javax.inject.Inject +import play.api.data.FormBinding import play.api.http._ import play.api.i18n.Langs import play.api.i18n.MessagesApi @@ -75,6 +76,8 @@ trait BaseControllerHelpers extends ControllerHelpers { */ def parse: PlayBodyParsers = controllerComponents.parsers + implicit val defaultFormBinding: FormBinding = parse.formBinding(parse.DefaultMaxTextLength) + /** * The default execution context provided by Play. You should use this for non-blocking code only. You can do so by * passing it explicitly, or by defining an implicit in your controller like so: diff --git a/documentation/manual/releases/release27/migration27/Migration27.md b/documentation/manual/releases/release27/migration27/Migration27.md index 3732a2a7c9f..649d336e15c 100644 --- a/documentation/manual/releases/release27/migration27/Migration27.md +++ b/documentation/manual/releases/release27/migration27/Migration27.md @@ -102,47 +102,26 @@ The API for body parser was mixing `Integer` and `Long` to define buffer lengths ### Parser `maxMemoryBuffer` limits -Some payloads expand in memory when being parsed. So, the memory representation takes more space than the plaintext representation read from the wire. JSON is one of these formats. In order to prevent attacks that could lead to out of memory errors causing Denial-of-Service, body parsing and form binding must honour the `play.http.parser.maxMemoruBuffer`. +Some payloads expand in memory when being parsed. So, the memory representation takes more space than the plaintext representation read from the wire. JSON is one of these formats. In order to prevent attacks that could lead to out of memory errors causing Denial-of-Service, body parsing and form binding must honour the `play.http.parser.maxMemoryBuffer` setting. -The value of `play.http.parser.maxMemoruBuffer` is honored ouf of the box. If you are a user of the Play Scala API, you will need to declare a new implicit value providing the apprpriate instance of a `FormBinding`: - -_Before_ - -```scala - val formValidationResult = form.bindFromRequest -``` - -_After_ +It is also possible to relax the `maxMemoryBuffer` in specific cases. It is possible the JSON representation and the expanded representation differ in size and you need to use different limits. You can use a form binding with a customized limit using: ```scala -// Assuming you have: class MyController @Inject()(cc: MessagesControllerComponents) { - implicit val fb = cc.parsers.defaultFormBinding - val formValidationResult = form.bindFromRequest ... -} -``` - -You can also use a form binding with a customized limit using: - -```scala -// Assuming you have: -class MyController @Inject()(cc: MessagesControllerComponents) { - implicit val fb = cc.parsers.formBinding(300*1024) // limit to 300KiB - val formValidationResult = form.bindFromRequest + // create a new formBinding instance with increased limit + val formBinding = cc.parsers.formBinding(300*1024) // limit to 300KiB + val formValidationResult = form.bindFromRequest()(request, formBinding) ... } ``` -Finally, in tests, you probably don't need to read the value from `Config` and a default, hardcoded value is fine. In that case you can simply: +Controllers will always have a `FormBinding` instance build to honor the `play.http.parser.maxMemoryBuffer`. If you use the Forms from code outside a Controller, you may need to provide a `FormBinding`. For example, is you write unit tests you can use a `FormBinding` provided in `play.api.data.FormBinding.Implicits._` which uses a hardcoded limit which is good enough for tests. Add the implicit in scope: ````scala import play.api.data.FormBinding.Implicits._ ``` -The `FormBinding.Implicits._` implicits can be used from production code but that is discouraged since they use hardcoded values that don't honor the `play.http.parser.maxMemoruBuffer` configuration. - - ### New fields and methods added to `FilePart` and `FileInfo` [`Scala's`](api/scala/play/api/mvc/MultipartFormData$$FilePart.html) and [`Java's`](api/java/play/mvc/Http.MultipartFormData.FilePart.html) `FilePart` classes have two new fields/methods which provide you the file size and the disposition type of a file that was uploaded via the `multipart/form-data` encoding: diff --git a/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala b/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala index 654f3cfffef..ef5f2fc58eb 100644 --- a/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala +++ b/documentation/manual/working/scalaGuide/main/forms/code/ScalaForms.scala @@ -31,7 +31,6 @@ package scalaguide.forms.scalaforms { // #validation-imports import play.api.data.validation.Constraints._ // #validation-imports - import play.api.data.FormBinding.Implicits._ @RunWith(classOf[JUnitRunner]) class ScalaFormsSpec extends Specification with ControllerHelpers { @@ -53,6 +52,7 @@ package scalaguide.forms.scalaforms { "generate from request" in new WithApplication { import play.api.libs.json.Json + import play.api.data.FormBinding.Implicits._ val controller = app.injector.instanceOf[controllers.Application] val userForm = controller.userForm @@ -97,6 +97,7 @@ package scalaguide.forms.scalaforms { val userForm = controller.userFormConstraints implicit val request = FakeRequest().withFormUrlEncodedBody("name" -> "", "age" -> "25") + import play.api.data.FormBinding.Implicits._ val boundForm = userForm.bindFromRequest boundForm.hasErrors must beTrue @@ -106,6 +107,7 @@ package scalaguide.forms.scalaforms { val controller = app.injector.instanceOf[controllers.Application] val userForm = controller.userFormConstraintsAdHoc + import play.api.data.FormBinding.Implicits._ implicit val request = FakeRequest().withFormUrlEncodedBody("name" -> "Johnny Utah", "age" -> "25") val boundForm = userForm.bindFromRequest diff --git a/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala b/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala index 1da912bb6fd..b53659cfc20 100644 --- a/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala +++ b/documentation/manual/working/scalaGuide/main/ws/code/ScalaOpenIdSpec.scala @@ -24,8 +24,6 @@ class IdController @Inject() (val openIdClient: OpenIdClient, c: ControllerCompo //#dependency class ScalaOpenIdSpec extends PlaySpecification { - import play.api.data.FormBinding.Implicits._ - "Scala OpenId" should { "be injectable" in new WithApplication() with Injecting { val controller = diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index e9752462300..8464ac3abf6 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -223,6 +223,12 @@ object BuildSettings { ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.defaultFormBinding"), ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding$default$1"), ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding"), + // fix types on Json parsing limits + ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.data.Form.bind"), + ProblemFilters.exclude[ReversedMissingMethodProblem]( + "play.api.mvc.BaseControllerHelpers.play$api$mvc$BaseControllerHelpers$_setter_$defaultFormBinding_=" + ), + ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.BaseControllerHelpers.defaultFormBinding"), ), unmanagedSourceDirectories in Compile += { val suffix = CrossVersion.partialVersion(scalaVersion.value) match { From 40e3e47cae8287ad290cc1823937ca1cd1e61074 Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Fri, 4 Dec 2020 12:46:31 +0100 Subject: [PATCH 13/15] Minor writeup and docs improvements --- .../release27/migration27/Migration27.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/documentation/manual/releases/release27/migration27/Migration27.md b/documentation/manual/releases/release27/migration27/Migration27.md index 649d336e15c..1206aa411f4 100644 --- a/documentation/manual/releases/release27/migration27/Migration27.md +++ b/documentation/manual/releases/release27/migration27/Migration27.md @@ -107,16 +107,20 @@ Some payloads expand in memory when being parsed. So, the memory representation It is also possible to relax the `maxMemoryBuffer` in specific cases. It is possible the JSON representation and the expanded representation differ in size and you need to use different limits. You can use a form binding with a customized limit using: ```scala -class MyController @Inject()(cc: MessagesControllerComponents) { - ... - // create a new formBinding instance with increased limit - val formBinding = cc.parsers.formBinding(300*1024) // limit to 300KiB - val formValidationResult = form.bindFromRequest()(request, formBinding) - ... +class MyController @Inject()(cc: ControllerComponents) { + + // This will be the action that handles our form post + def myMethod = Action { implicit request: Request[_] => + // create a new formBinding instance with increased limit + val defaultFormBinding: FormBinding = cc.parsers.formBinding(300*1024) // limit to 300KiB + form.bindFromRequest()(request, formBinding) + ... + } + } ``` -Controllers will always have a `FormBinding` instance build to honor the `play.http.parser.maxMemoryBuffer`. If you use the Forms from code outside a Controller, you may need to provide a `FormBinding`. For example, is you write unit tests you can use a `FormBinding` provided in `play.api.data.FormBinding.Implicits._` which uses a hardcoded limit which is good enough for tests. Add the implicit in scope: +Controllers will always have a `FormBinding` instance build to honor the `play.http.parser.maxMemoryBuffer`. If you use your forms from some code outside a Controller, you may need to provide an implicit `FormBinding`. For example, if you write unit tests you can use the `FormBinding` provided in `play.api.data.FormBinding.Implicits._` which uses a hardcoded limit that is good enough for tests. Add the implicit in scope: ````scala import play.api.data.FormBinding.Implicits._ From 9ca5182dee99df5d4d8000c47a790f09f855ed5d Mon Sep 17 00:00:00 2001 From: Ignasi Marimon-Clos Date: Fri, 4 Dec 2020 14:18:02 +0100 Subject: [PATCH 14/15] mima --- project/BuildSettings.scala | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index 8464ac3abf6..51ad6951cd4 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -216,15 +216,7 @@ object BuildSettings { ProblemFilters.exclude[MissingClassProblem]("play.core.server.ssl.FakeKeyStore$"), ProblemFilters.exclude[MissingClassProblem]("play.core.server.ssl.FakeKeyStore"), // Honour maxMemoryBuffer when binding Json to form - ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.data.Form.bindFromRequest"), - ProblemFilters.exclude[ReversedMissingMethodProblem]( - "play.api.mvc.PlayBodyParsers.play$api$mvc$PlayBodyParsers$_setter_$defaultFormBinding_=" - ), - ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.defaultFormBinding"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding$default$1"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("play.api.mvc.PlayBodyParsers.formBinding"), - // fix types on Json parsing limits - ProblemFilters.exclude[IncompatibleMethTypeProblem]("play.api.data.Form.bind"), + ProblemFilters.exclude[DirectMissingMethodProblem]("play.api.data.Form.bindFromRequest"), ProblemFilters.exclude[ReversedMissingMethodProblem]( "play.api.mvc.BaseControllerHelpers.play$api$mvc$BaseControllerHelpers$_setter_$defaultFormBinding_=" ), From e67adb62f309995830dc08d2484a1fcdd9e3fe77 Mon Sep 17 00:00:00 2001 From: Enno Runne <458526+ennru@users.noreply.github.com> Date: Wed, 9 Dec 2020 15:16:29 +0100 Subject: [PATCH 15/15] Play File Watch 1.1.14 (was 1.1.13) for sbt 0.13 --- project/Dependencies.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index adb8b56dd12..029247859ab 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -185,7 +185,7 @@ object Dependencies { ) } - val playFileWatch = "com.lightbend.play" %% "play-file-watch" % "1.1.13" + val playFileWatch = "com.lightbend.play" %% "play-file-watch" % "1.1.14" def runSupportDependencies(sbtVersion: String): Seq[ModuleID] = { (CrossVersion.binarySbtVersion(sbtVersion) match {