diff --git a/.github/workflows/dependency-graph.yml b/.github/workflows/dependency-graph.yml index 46d7f5c408..eb87131010 100644 --- a/.github/workflows/dependency-graph.yml +++ b/.github/workflows/dependency-graph.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - uses: scalacenter/sbt-dependency-submission@f43202114d7522a4b233e052f82c2eea8d658134 # v3.2.1 with: modules-ignore: pekko-http-tests_3 pekko-http-docs_3 diff --git a/.github/workflows/headers.yml b/.github/workflows/headers.yml index e152f1f11a..127f62627d 100644 --- a/.github/workflows/headers.yml +++ b/.github/workflows/headers.yml @@ -37,10 +37,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Enable jvm-opts run: cp .jvmopts-ci .jvmopts diff --git a/.github/workflows/link-validator.yml b/.github/workflows/link-validator.yml index 6a377689db..ae383c0647 100644 --- a/.github/workflows/link-validator.yml +++ b/.github/workflows/link-validator.yml @@ -38,10 +38,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Setup Coursier uses: coursier/setup-action@fd1707a76b027efdfb66ca79318b4d29b72e5a02 # v3.0.0 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index d5e49b1233..7651389765 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -38,13 +38,13 @@ jobs: java-version: ${{ matrix.JDK }} - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Cache Build Target - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: project/**/target key: build-target-${{ hashFiles('**/*.sbt', 'project/build.properties', 'project/**/*.scala') }} diff --git a/.github/workflows/publish-1.0-docs.yml b/.github/workflows/publish-1.0-docs.yml index f6f04988a1..1b70f94065 100644 --- a/.github/workflows/publish-1.0-docs.yml +++ b/.github/workflows/publish-1.0-docs.yml @@ -40,10 +40,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Build Documentation run: |- diff --git a/.github/workflows/publish-1.0-snapshots.yml b/.github/workflows/publish-1.0-snapshots.yml index 5d96cd06a5..ecc2b95e02 100644 --- a/.github/workflows/publish-1.0-snapshots.yml +++ b/.github/workflows/publish-1.0-snapshots.yml @@ -33,10 +33,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Install graphviz run: sudo apt-get install -y graphviz diff --git a/.github/workflows/publish-1.1-docs.yml b/.github/workflows/publish-1.1-docs.yml index 2f8c0f76a4..751585fffd 100644 --- a/.github/workflows/publish-1.1-docs.yml +++ b/.github/workflows/publish-1.1-docs.yml @@ -40,10 +40,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Build Documentation run: |- diff --git a/.github/workflows/publish-1.1-snapshots.yml b/.github/workflows/publish-1.1-snapshots.yml index b5e8e689ef..f7688938ca 100644 --- a/.github/workflows/publish-1.1-snapshots.yml +++ b/.github/workflows/publish-1.1-snapshots.yml @@ -33,10 +33,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Install graphviz run: sudo apt-get install -y graphviz diff --git a/.github/workflows/publish-1.2-docs.yml b/.github/workflows/publish-1.2-docs.yml index 882394b274..7c73ec300d 100644 --- a/.github/workflows/publish-1.2-docs.yml +++ b/.github/workflows/publish-1.2-docs.yml @@ -39,10 +39,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@v6 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Build Documentation run: |- diff --git a/.github/workflows/publish-1.3-docs.yml b/.github/workflows/publish-1.3-docs.yml index d3e36c9490..85de496879 100644 --- a/.github/workflows/publish-1.3-docs.yml +++ b/.github/workflows/publish-1.3-docs.yml @@ -40,10 +40,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@6c68d2fe8dfbc0a0534d70101baa2e0420e1a506 # v1.1.9 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@v6 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Build Documentation run: |- diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8332f83328..e303ecaa10 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -41,10 +41,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Install graphviz run: sudo apt-get install -y graphviz @@ -74,10 +74,10 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Build Documentation run: |- diff --git a/.github/workflows/stage-release-candidate.yml b/.github/workflows/stage-release-candidate.yml index d0abc8fdf6..613cf1bf48 100644 --- a/.github/workflows/stage-release-candidate.yml +++ b/.github/workflows/stage-release-candidate.yml @@ -212,7 +212,7 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Install Graphviz run: |- diff --git a/.github/workflows/validate-and-test.yml b/.github/workflows/validate-and-test.yml index fe48f6304f..e440184c6a 100644 --- a/.github/workflows/validate-and-test.yml +++ b/.github/workflows/validate-and-test.yml @@ -38,13 +38,13 @@ jobs: java-version: 8 - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Cache Build Target - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: project/**/target key: build-target-${{ hashFiles('**/*.sbt', 'project/build.properties', 'project/**/*.scala') }} @@ -86,13 +86,13 @@ jobs: java-version: ${{ matrix.JDK }} - name: Install sbt - uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 + uses: sbt/setup-sbt@8feba82adc7f01ddcf8165b86f778bdb5b82cebc # v1.5.7 - name: Cache Coursier cache - uses: coursier/cache-action@90c37294538be80a558fd665531fcdc2b467b475 # v8.1.0 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 - name: Cache Build Target - uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: project/**/target key: build-target-${{ hashFiles('**/*.sbt', 'project/build.properties', 'project/**/*.scala') }} diff --git a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2Blueprint.scala b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2Blueprint.scala index 3b14c0b86b..f732085b05 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2Blueprint.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/impl/engine/http2/Http2Blueprint.scala @@ -38,8 +38,8 @@ import pekko.http.scaladsl.settings.{ import pekko.stream.{ BidiShape, Graph, StreamTcpException, ThrottleMode } import pekko.stream.TLSProtocol._ import pekko.stream.scaladsl.{ BidiFlow, Flow, Keep, Source } -import pekko.util.ByteString -import pekko.util.OptionVal +import pekko.util.{ ByteString, OptionVal } +import pekko.util.Helpers.toRootLowerCase import scala.concurrent.duration.{ Duration, FiniteDuration } import scala.concurrent.{ ExecutionContext, Future } @@ -226,7 +226,7 @@ private[http] object Http2Blueprint { } private[http2] def frameTypeAliasToFrameTypeName(frameType: String): Option[String] = { - frameType.toLowerCase match { + toRootLowerCase(frameType) match { case "reset" => Some("RstStreamFrame") case "headers" => Some("HeadersFrame") case "continuation" => Some("ContinuationFrame") diff --git a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala index c8999d0ce3..3a47781d17 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/impl/settings/ParserSettingsImpl.scala @@ -23,6 +23,7 @@ import pekko.http.scaladsl.settings.ParserSettings.{ IllegalResponseHeaderValueProcessingMode } import pekko.util.ConstantFun +import pekko.util.Helpers.toRootLowerCase import com.typesafe.config.Config import scala.collection.JavaConverters._ @@ -115,7 +116,7 @@ object ParserSettingsImpl extends SettingsCompanionImpl[ParserSettingsImpl]("pek Uri.ParsingMode(c.getString("uri-parsing-mode")), CookieParsingMode(c.getString("cookie-parsing-mode")), c.getBoolean("illegal-header-warnings"), - c.getStringList("ignore-illegal-header-for").asScala.map(_.toLowerCase).toSet, + c.getStringList("ignore-illegal-header-for").asScala.map(toRootLowerCase).toSet, ErrorLoggingVerbosity(c.getString("error-logging-verbosity")), IllegalResponseHeaderNameProcessingMode(c.getString("illegal-response-header-name-processing-mode")), IllegalResponseHeaderValueProcessingMode(c.getString("illegal-response-header-value-processing-mode")), diff --git a/http-core/src/main/scala/org/apache/pekko/http/javadsl/ConnectHttp.scala b/http-core/src/main/scala/org/apache/pekko/http/javadsl/ConnectHttp.scala index 7191d01827..27d3bc9990 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/javadsl/ConnectHttp.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/javadsl/ConnectHttp.scala @@ -13,12 +13,12 @@ package org.apache.pekko.http.javadsl -import java.util.Locale import java.util.Optional import org.apache.pekko import pekko.annotation.{ DoNotInherit, InternalApi } import pekko.http.javadsl.model.Uri +import pekko.util.Helpers.toRootLowerCase import pekko.util.OptionConverters._ @DoNotInherit @@ -71,7 +71,7 @@ object ConnectHttp { } private def toHost(uriHost: Uri, port: Int): ConnectHttp = { - val s = uriHost.scheme.toLowerCase(Locale.ROOT) + val s = toRootLowerCase(uriHost.scheme) if (s == "https") new ConnectHttpsImpl(uriHost.host.address, effectivePort(s, port), context = Optional.empty()) else new ConnectHttpImpl(uriHost.host.address, effectivePort(s, port)) } @@ -114,7 +114,7 @@ object ConnectHttp { } private def toHostHttps(uriHost: Uri, port: Int): ConnectWithHttps = { - val s = uriHost.scheme.toLowerCase(Locale.ROOT) + val s = toRootLowerCase(uriHost.scheme) require(s == "" || s == "https", "toHostHttps used with non https scheme! Was: " + uriHost) new ConnectHttpsImpl(uriHost.host.address, effectivePort("https", port), context = Optional.empty()) } @@ -125,7 +125,7 @@ object ConnectHttp { } private def effectivePort(scheme: String, port: Int): Int = { - val s = scheme.toLowerCase(Locale.ROOT) + val s = toRootLowerCase(scheme) if (port >= 0) port else if (s == "https" || s == "wss") 443 else if (s == "http" || s == "ws") 80 diff --git a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala index 6eaa2adedb..2b0b3240c4 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/javadsl/settings/ParserSettings.scala @@ -23,6 +23,7 @@ import java.{ util => ju } import pekko.annotation.DoNotInherit import pekko.http.impl.util.JavaMapping.Implicits._ +import pekko.util.Helpers.toRootLowerCase import scala.annotation.varargs import scala.collection.JavaConverters._ @@ -90,7 +91,7 @@ abstract class ParserSettings private[pekko] () extends BodyPartParser.Settings self.copy(includeSslSessionAttribute = newValue) def withModeledHeaderParsing(newValue: Boolean): ParserSettings = self.copy(modeledHeaderParsing = newValue) def withIgnoreIllegalHeaderFor(newValue: List[String]): ParserSettings = - self.copy(ignoreIllegalHeaderFor = newValue.map(_.toLowerCase).toSet) + self.copy(ignoreIllegalHeaderFor = newValue.map(toRootLowerCase).toSet) // special --- diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/ErrorInfo.scala b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/ErrorInfo.scala index 5d73947b54..2b552fe42a 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/ErrorInfo.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/ErrorInfo.scala @@ -14,7 +14,9 @@ package org.apache.pekko.http.scaladsl.model import StatusCodes.ClientError -import org.apache.pekko.annotation.InternalApi +import org.apache.pekko +import pekko.annotation.InternalApi +import pekko.util.Helpers.toRootLowerCase /** * Two-level model of error information. @@ -28,7 +30,7 @@ final class ErrorInfo( val errorHeaderName: String = "") extends scala.Product with scala.Equals with java.io.Serializable { def withSummary(newSummary: String) = copy(summary = newSummary) def withSummaryPrepended(prefix: String) = withSummary(if (summary.isEmpty) prefix else prefix + ": " + summary) - def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, headerName.toLowerCase) + def withErrorHeaderName(headerName: String) = new ErrorInfo(summary, detail, toRootLowerCase(headerName)) def withFallbackSummary(fallbackSummary: String) = if (summary.isEmpty) withSummary(fallbackSummary) else this def formatPretty = if (summary.isEmpty) detail else if (detail.isEmpty) summary else summary + ": " + detail def format(withDetail: Boolean): String = if (withDetail) formatPretty else summary diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/HttpHeader.scala b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/HttpHeader.scala index 0434b557de..6a2a47a027 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/HttpHeader.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/HttpHeader.scala @@ -23,6 +23,7 @@ import pekko.http.impl.model.parser.{ CharacterClasses, HeaderParser } import pekko.http.javadsl.{ model => jm } import pekko.http.scaladsl.model.headers._ import pekko.util.OptionVal +import pekko.util.Helpers.toRootLowerCase import scala.collection.immutable @@ -91,7 +92,7 @@ object HttpHeader { val parser = new HeaderParser(value, settings) parser.`header-field-value`.run() match { case Success(preProcessedValue) => - HeaderParser.parseFull(name.toLowerCase, preProcessedValue, settings) match { + HeaderParser.parseFull(toRootLowerCase(name), preProcessedValue, settings) match { case HeaderParser.Success(header) => ParsingResult.Ok(header, Nil) case HeaderParser.Failure(info) => val errors = info.withSummaryPrepended(s"Illegal HTTP header '$name'") :: Nil diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/MediaType.scala b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/MediaType.scala index 61643de19c..7eef53b92b 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/MediaType.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/MediaType.scala @@ -18,6 +18,7 @@ import pekko.annotation.DoNotInherit import pekko.http.impl.util._ import pekko.http.javadsl.{ model => jm } import pekko.http.impl.util.JavaMapping.Implicits._ +import pekko.util.Helpers.toRootLowerCase /** * A MediaType describes the type of the content of an HTTP message entity. @@ -74,7 +75,7 @@ sealed abstract class MediaType(_mainType: String, _subType: String) extends jm. case _ => false } - override def hashCode(): Int = value.toLowerCase.hashCode + override def hashCode(): Int = toRootLowerCase(value).hashCode /** * JAVA API @@ -327,12 +328,12 @@ object MediaTypes extends ObjectRegistry[(String, String), MediaType] { private[this] var extensionMap = Map.empty[String, MediaType] - def forExtensionOption(ext: String): Option[MediaType] = extensionMap.get(ext.toLowerCase) - def forExtension(ext: String): MediaType = extensionMap.getOrElse(ext.toLowerCase, `application/octet-stream`) + def forExtensionOption(ext: String): Option[MediaType] = extensionMap.get(toRootLowerCase(ext)) + def forExtension(ext: String): MediaType = extensionMap.getOrElse(toRootLowerCase(ext), `application/octet-stream`) private def registerFileExtensions[T <: MediaType](mediaType: T): T = { mediaType.fileExtensions.foreach { ext => - val lcExt = ext.toLowerCase + val lcExt = toRootLowerCase(ext) require(!extensionMap.contains(lcExt), s"Extension '$ext' clash: media-types '${extensionMap(lcExt)}' and '$mediaType'") extensionMap = extensionMap.updated(lcExt, mediaType) diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/Uri.scala b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/Uri.scala index 72e4e7f9d0..135ac4cf95 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/Uri.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/model/Uri.scala @@ -28,6 +28,7 @@ import pekko.http.javadsl.{ model => jm } import pekko.http.impl.model.parser.UriParser import pekko.http.impl.model.parser.CharacterClasses._ import pekko.http.impl.util._ +import pekko.util.Helpers.toRootLowerCase import Uri._ /** @@ -868,7 +869,7 @@ object Uri { } else if (allLower) -1 else -2 verify() match { - case -2 => scheme.toLowerCase + case -2 => toRootLowerCase(scheme) case -1 => scheme case ix => fail(s"Invalid URI scheme, unexpected character at pos $ix ('${scheme.charAt(ix)}')") } diff --git a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala index 82b919b989..980d44c9a3 100644 --- a/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala +++ b/http-core/src/main/scala/org/apache/pekko/http/scaladsl/settings/ParserSettings.scala @@ -25,6 +25,7 @@ import pekko.http.impl.util._ import pekko.http.javadsl.model import pekko.http.scaladsl.model._ import pekko.http.scaladsl.{ settings => js } +import pekko.util.Helpers.toRootLowerCase import pekko.util.OptionConverters._ import com.typesafe.config.Config @@ -123,7 +124,7 @@ abstract class ParserSettings private[pekko] () extends pekko.http.javadsl.setti self.copy(includeSslSessionAttribute = newValue) override def withModeledHeaderParsing(newValue: Boolean): ParserSettings = self.copy(modeledHeaderParsing = newValue) override def withIgnoreIllegalHeaderFor(newValue: List[String]): ParserSettings = - self.copy(ignoreIllegalHeaderFor = newValue.map(_.toLowerCase).toSet) + self.copy(ignoreIllegalHeaderFor = newValue.map(toRootLowerCase).toSet) // overloads for idiomatic Scala use def withUriParsingMode(newValue: Uri.ParsingMode): ParserSettings = self.copy(uriParsingMode = newValue) diff --git a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/model/TurkishISpec.scala b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/model/TurkishISpec.scala index 58d6a89f69..76f9fdede6 100644 --- a/http-core/src/test/scala/org/apache/pekko/http/scaladsl/model/TurkishISpec.scala +++ b/http-core/src/test/scala/org/apache/pekko/http/scaladsl/model/TurkishISpec.scala @@ -40,5 +40,34 @@ class TurkishISpec extends AnyWordSpec with Matchers { Locale.setDefault(previousLocale) } } + + "parse a header name containing a capital I in the turkish locale" in withTurkishLocale { + HttpHeader.parse("If-Match", "\"xyzzy\"") match { + case HttpHeader.ParsingResult.Ok(header, Nil) => header shouldBe a[headers.`If-Match`] + case other => fail(s"Expected a modelled If-Match header but got $other") + } + } + + "normalize a uri scheme containing a capital I in the turkish locale" in withTurkishLocale { + Uri(scheme = "IPP", authority = Uri.Authority(Uri.Host("example.com"))).scheme shouldEqual "ipp" + } + + "resolve a media type for an upper case file extension in the turkish locale" in withTurkishLocale { + MediaTypes.forExtension("TIFF") shouldEqual MediaTypes.`image/tiff` + } + + "lowercase an error header name in the turkish locale" in withTurkishLocale { + ErrorInfo("summary", "detail").withErrorHeaderName("If-Match").errorHeaderName shouldEqual "if-match" + } + } + + private def withTurkishLocale(body: => Any): Unit = { + val previousLocale = Locale.getDefault + try { + Locale.setDefault(new Locale("tr", "TR")) + body + } finally { + Locale.setDefault(previousLocale) + } } } diff --git a/http-testkit/src/main/scala/org/apache/pekko/http/scaladsl/testkit/RouteTest.scala b/http-testkit/src/main/scala/org/apache/pekko/http/scaladsl/testkit/RouteTest.scala index c33ca493a8..bf961219f8 100644 --- a/http-testkit/src/main/scala/org/apache/pekko/http/scaladsl/testkit/RouteTest.scala +++ b/http-testkit/src/main/scala/org/apache/pekko/http/scaladsl/testkit/RouteTest.scala @@ -30,6 +30,7 @@ import pekko.stream.{ Materializer, SystemMaterializer } import pekko.stream.scaladsl.Source import pekko.testkit.TestKit import pekko.util.ConstantFun +import pekko.util.Helpers.toRootLowerCase import com.typesafe.config.{ Config, ConfigFactory } import scala.collection.immutable @@ -95,7 +96,7 @@ trait RouteTest extends RequestBuilding with WSTestRequestBuilding with RouteTes def charset: HttpCharset = charsetOption.getOrElse(sys.error("Binary entity does not have charset")) def headers: immutable.Seq[HttpHeader] = rawResponse.headers def header[T >: Null <: HttpHeader: ClassTag]: Option[T] = rawResponse.header[T](implicitly[ClassTag[T]]) - def header(name: String): Option[HttpHeader] = rawResponse.headers.find(_.is(name.toLowerCase)) + def header(name: String): Option[HttpHeader] = rawResponse.headers.find(_.is(toRootLowerCase(name))) def status: StatusCode = rawResponse.status def closingExtension: String = chunks.lastOption match { diff --git a/http-testkit/src/test/scala/org/apache/pekko/http/scaladsl/testkit/ScalatestRouteTestSpec.scala b/http-testkit/src/test/scala/org/apache/pekko/http/scaladsl/testkit/ScalatestRouteTestSpec.scala index 63eb780edb..4ef079b49d 100644 --- a/http-testkit/src/test/scala/org/apache/pekko/http/scaladsl/testkit/ScalatestRouteTestSpec.scala +++ b/http-testkit/src/test/scala/org/apache/pekko/http/scaladsl/testkit/ScalatestRouteTestSpec.scala @@ -13,6 +13,8 @@ package org.apache.pekko.http.scaladsl.testkit +import java.util.Locale + import scala.concurrent.duration._ import org.apache.pekko import pekko.testkit._ @@ -57,6 +59,25 @@ class ScalatestRouteTestSpec extends AnyFreeSpec with Matchers with ScalatestRou } } + "a header lookup by name that is unaffected by the turkish-i problem" in { + val previousLocale = Locale.getDefault + try { + Locale.setDefault(new Locale("tr", "TR")) + // in the turkish locale 'I'.toLowerCase is a dotless i, so a default-locale + // lowercasing of 'If-Match' would not match the header's lowercaseName + val ifMatchHeader = RawHeader("If-Match", "\"xyzzy\"") + Get() ~> { + respondWithHeader(ifMatchHeader) { + complete("abc") + } + } ~> check { + header("If-Match") shouldEqual Some(ifMatchHeader) + } + } finally { + Locale.setDefault(previousLocale) + } + } + "a test using ~!> and some checks" in { // raw here, should have been parsed into modelled header when going through an actual server when using `~!>` val extraHeader = RawHeader("X-Forwarded-Proto", "abc") diff --git a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/HeaderDirectives.scala b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/HeaderDirectives.scala index 54c4d9fa15..29d94b920c 100644 --- a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/HeaderDirectives.scala +++ b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/HeaderDirectives.scala @@ -18,6 +18,7 @@ import org.apache.pekko import pekko.http.impl.util._ import pekko.http.scaladsl.model._ import pekko.http.scaladsl.model.headers._ +import pekko.util.Helpers.toRootLowerCase import scala.reflect.ClassTag import scala.util.control.NonFatal @@ -92,7 +93,7 @@ trait HeaderDirectives { * @group header */ def headerValueByName(headerName: String): Directive1[String] = - headerValue(optionalValue(headerName.toLowerCase)) | reject(MissingHeaderRejection(headerName)) + headerValue(optionalValue(toRootLowerCase(headerName))) | reject(MissingHeaderRejection(headerName)) /** * Extracts the first HTTP request header of the given type. @@ -147,7 +148,7 @@ trait HeaderDirectives { * @group header */ def optionalHeaderValueByName(headerName: String): Directive1[Option[String]] = { - val lowerCaseName = headerName.toRootLowerCase + val lowerCaseName = toRootLowerCase(headerName) extract(_.request.headers.collectFirst { case h: HttpHeader if h.is(lowerCaseName) => h.value }) diff --git a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/MethodDirectives.scala b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/MethodDirectives.scala index 7edf7c6e01..3d609d957f 100644 --- a/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/MethodDirectives.scala +++ b/http/src/main/scala/org/apache/pekko/http/scaladsl/server/directives/MethodDirectives.scala @@ -14,6 +14,8 @@ package org.apache.pekko.http.scaladsl.server package directives +import java.util.Locale + import org.apache.pekko import pekko.http.scaladsl.model.{ HttpMethod, StatusCodes } import pekko.http.scaladsl.model.HttpMethods._ @@ -111,7 +113,7 @@ trait MethodDirectives { def overrideMethodWithParameter(paramName: String): Directive0 = parameter(paramName.optional).flatMap { case Some(method) => - getForKey(method.toUpperCase) match { + getForKey(method.toUpperCase(Locale.ROOT)) match { case Some(m) => mapRequest(_.withMethod(m)) case _ => complete(StatusCodes.NotImplemented) } diff --git a/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala b/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala index a865fcedb2..e18eab4bbb 100755 --- a/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala +++ b/http/src/main/scala/org/apache/pekko/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala @@ -19,6 +19,7 @@ import scala.collection.immutable import org.apache.pekko import pekko.http.scaladsl.util.FastFuture import pekko.util.ByteString +import pekko.util.Helpers.toRootLowerCase trait PredefinedFromStringUnmarshallers { @@ -48,7 +49,7 @@ trait PredefinedFromStringUnmarshallers { implicit val booleanFromStringUnmarshaller: Unmarshaller[String, Boolean] = Unmarshaller.strict[String, Boolean] { string => - string.toLowerCase match { + toRootLowerCase(string) match { case "true" | "yes" | "on" | "1" => true case "false" | "no" | "off" | "0" => false case "" => throw Unmarshaller.NoContentException