From ab3576136927703586828e8bd8a1edaea1e07a31 Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 4 Sep 2013 14:58:45 +0200 Subject: [PATCH] ! routing: fix `getFromDirectory` and `getFromResourceDirectory` not working properly for URIs with encoded chars Previously the unmatched path of the request context was directly used for retrieving the file/resource content. If the path contains non-token chars that need to be encoded this results in file system/resource accesses to something like `foo%20bar.txt` instead of `foo bar.txt`. This patch fixes this problem. The "breakingness" of this patch results from marking two internal methods private that were previously (and erroneously) public. --- .../FileAndResourceDirectives.scala | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala b/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala index debacc8db0..907d96b3d9 100644 --- a/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala +++ b/spray-routing/src/main/scala/spray/routing/directives/FileAndResourceDirectives.scala @@ -19,6 +19,7 @@ package directives import java.io.File import org.parboiled.common.FileUtils +import scala.annotation.tailrec import akka.actor.ActorRefFactory import spray.httpx.marshalling.{ Marshaller, BasicMarshallers } import spray.util._ @@ -131,7 +132,10 @@ trait FileAndResourceDirectives { refFactory: ActorRefFactory, log: LoggingContext): Route = { val base = withTrailingSlash(directoryName) unmatchedPath { path ⇒ - getFromFile(base + stripLeadingSlash(path)) + fileSystemPath(base, path) match { + case "" ⇒ reject + case fileName ⇒ getFromFile(fileName) + } } } @@ -183,14 +187,31 @@ trait FileAndResourceDirectives { refFactory: ActorRefFactory, log: LoggingContext): Route = { val base = if (directoryName.isEmpty) "" else withTrailingSlash(directoryName) unmatchedPath { path ⇒ - getFromResource(base + stripLeadingSlash(path).toString) + fileSystemPath(base, path, separator = '/') match { + case "" ⇒ reject + case resourceName ⇒ getFromResource(resourceName) + } } } } object FileAndResourceDirectives extends FileAndResourceDirectives { - def stripLeadingSlash(path: Uri.Path) = if (path.startsWithSlash) path.tail else path - def withTrailingSlash(path: String) = if (path endsWith "/") path else path + '/' + private def withTrailingSlash(path: String): String = if (path endsWith "/") path else path + '/' + private def fileSystemPath(base: String, path: Uri.Path, separator: Char = File.separatorChar)(implicit log: LoggingContext): String = { + import java.lang.StringBuilder + @tailrec def rec(p: Uri.Path, result: StringBuilder = new StringBuilder(base)): String = + p match { + case Uri.Path.Empty ⇒ result.toString + case Uri.Path.Slash(tail) ⇒ rec(tail, result.append(separator)) + case Uri.Path.Segment(head, tail) ⇒ + if (head.indexOf('/') >= 0 || head == "..") { + log.warning("File-system path for base [{}] and Uri.Path [{}] contains suspicious path segment [{}], " + + "GET access was disallowed", base, path, head) + "" + } else rec(tail, result.append(head)) + } + rec(if (path.startsWithSlash) path.tail else path) + } } trait ContentTypeResolver {