{"action":"created","comment":{"url":"https://api.github.com/repos/akka/akka-http/pulls/comments/80508931","id":80508931,"diff_hunk":"@@ -19,108 +21,136 @@ import akka.http.scaladsl.model.MediaTypes._\n import akka.http.scaladsl.model._\n import akka.http.scaladsl.model.headers._\n import akka.http.impl.util._\n-import akka.http.scaladsl.TestUtils.writeAllText\n \n class FileAndResourceDirectivesSpec extends RoutingSpec with Inspectors with Inside {\n \n   // operations touch files, can be randomly hit by slowness\n   implicit val routeTestTimeout = RouteTestTimeout(3.seconds)\n \n+  val base = Paths.get(getClass.getClassLoader.getResource(\"\").toURI)\n+\n   override def testConfigSource = \"akka.http.routing.range-coalescing-threshold = 1\"\n \n   \"getFromFile\" should {\n+    def _getFromFile(file: String) = getFromFile(base.resolve(file))\n+\n     \"reject non-GET requests\" in {\n-      Put() ~> getFromFile(\"some\") ~> check { handled shouldEqual false }\n+      Put() ~> _getFromFile(\"some\") ~> check { handled shouldEqual false }\n     }\n     \"reject requests to non-existing files\" in {\n-      Get() ~> getFromFile(\"nonExistentFile\") ~> check { handled shouldEqual false }\n+      Get() ~> _getFromFile(\"nonExistentFile\") ~> check { handled shouldEqual false }\n     }\n     \"reject requests to directories\" in {\n-      Get() ~> getFromFile(Properties.javaHome) ~> check { handled shouldEqual false }\n+      Get() ~> _getFromFile(Properties.javaHome) ~> check { handled shouldEqual false }\n     }\n     \"return the file content with the MediaType matching the file extension\" in {\n-      val file = File.createTempFile(\"akka Http Test\", \".PDF\")\n-      try {\n-        writeAllText(\"This is PDF\", file)\n-        Get() ~> getFromFile(file.getPath) ~> check {\n-          mediaType shouldEqual `application/pdf`\n-          charsetOption shouldEqual None\n-          responseAs[String] shouldEqual \"This is PDF\"\n-          headers should contain(`Last-Modified`(DateTime(file.lastModified)))\n-        }\n-      } finally file.delete\n+      Get() ~> _getFromFile(\"sample space.pdf\") ~> check {\n+        mediaType shouldEqual `application/pdf`\n+        charsetOption shouldEqual None\n+        responseAs[String] shouldEqual \"This is PDF\"\n+        val lastModified =\n+          readAttributes(base.resolve(\"sample space.pdf\"), classOf[BasicFileAttributes]).lastModifiedTime().toMillis\n+        headers should contain(`Last-Modified`(DateTime(lastModified)))\n+      }\n     }\n     \"return the file content with MediaType 'application/octet-stream' on unknown file extensions\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", null)\n-      try {\n-        writeAllText(\"Some content\", file)\n-        Get() ~> getFromFile(file) ~> check {\n-          mediaType shouldEqual `application/octet-stream`\n-          responseAs[String] shouldEqual \"Some content\"\n-        }\n-      } finally file.delete\n+      Get() ~> _getFromFile(\"content\") ~> check {\n+        mediaType shouldEqual `application/octet-stream`\n+        responseAs[String] shouldEqual \"Some content\"\n+      }\n     }\n \n     \"return a single range from a file\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", null)\n-      try {\n-        writeAllText(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", file)\n-        Get() ~> addHeader(Range(ByteRange(0, 10))) ~> getFromFile(file) ~> check {\n-          status shouldEqual StatusCodes.PartialContent\n-          headers should contain(`Content-Range`(ContentRange(0, 10, 26)))\n-          responseAs[String] shouldEqual \"ABCDEFGHIJK\"\n-        }\n-      } finally file.delete\n+      Get() ~> addHeader(Range(ByteRange(0, 10))) ~> _getFromFile(\"abcd\") ~> check {\n+        status shouldEqual StatusCodes.PartialContent\n+        headers should contain(`Content-Range`(ContentRange(0, 10, 26)))\n+        responseAs[String] shouldEqual \"ABCDEFGHIJK\"\n+      }\n     }\n \n     \"return multiple ranges from a file at once\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", null)\n-      try {\n-        writeAllText(\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\", file)\n-        val rangeHeader = Range(ByteRange(1, 10), ByteRange.suffix(10))\n-        Get() ~> addHeader(rangeHeader) ~> getFromFile(file, ContentTypes.`text/plain(UTF-8)`) ~> check {\n-          status shouldEqual StatusCodes.PartialContent\n-          header[`Content-Range`] shouldEqual None\n-          mediaType.withParams(Map.empty) shouldEqual `multipart/byteranges`\n+      val rangeHeader = Range(ByteRange(1, 10), ByteRange.suffix(10))\n+      Get() ~> addHeader(rangeHeader) ~> getFromFile(base.resolve(\"abcd\"), ContentTypes.`text/plain(UTF-8)`) ~> check {\n+        status shouldEqual StatusCodes.PartialContent\n+        header[`Content-Range`] shouldEqual None\n+        mediaType.withParams(Map.empty) shouldEqual `multipart/byteranges`\n \n-          val parts = responseAs[Multipart.ByteRanges].toStrict(1.second).awaitResult(3.seconds).strictParts\n-          parts.map(_.entity.data.utf8String) should contain theSameElementsAs List(\"BCDEFGHIJK\", \"QRSTUVWXYZ\")\n-        }\n-      } finally file.delete\n+        val parts = responseAs[Multipart.ByteRanges].toStrict(1.second).awaitResult(3.seconds).strictParts\n+        parts.map(_.entity.data.utf8String) should contain theSameElementsAs List(\"BCDEFGHIJK\", \"QRSTUVWXYZ\")\n+      }\n     }\n \n     \"properly handle zero-byte files\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", null)\n-      try {\n-        Get() ~> getFromFile(file) ~> check {\n-          mediaType shouldEqual NoMediaType\n-          responseAs[String] shouldEqual \"\"\n-        }\n-      } finally file.delete\n+      Get() ~> _getFromFile(\"empty\") ~> check {\n+        mediaType shouldEqual NoMediaType\n+        responseAs[String] shouldEqual \"\"\n+      }\n     }\n \n     \"support precompressed files with registered MediaType\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", \".svgz\")\n-      try {\n-        writeAllText(\"123\", file)\n-        Get() ~> getFromFile(file) ~> check {\n-          mediaType shouldEqual `image/svg+xml`\n-          header[`Content-Encoding`] shouldEqual Some(`Content-Encoding`(HttpEncodings.gzip))\n-          responseAs[String] shouldEqual \"123\"\n-        }\n-      } finally file.delete\n+      Get() ~> _getFromFile(\"sample.svgz\") ~> check {\n+        mediaType shouldEqual `image/svg+xml`\n+        header[`Content-Encoding`] shouldEqual Some(`Content-Encoding`(HttpEncodings.gzip))\n+        responseAs[String] shouldEqual \"123\"\n+      }\n     }\n \n     \"support files with registered MediaType and .gz suffix\" in {\n-      val file = File.createTempFile(\"akkaHttpTest\", \".js.gz\")\n+      Get() ~> _getFromFile(\"sample.js.gz\") ~> check {\n+        mediaType shouldEqual `application/javascript`\n+        header[`Content-Encoding`] shouldEqual Some(`Content-Encoding`(HttpEncodings.gzip))\n+        responseAs[String] shouldEqual \"456\"\n+      }\n+    }\n+  }\n+\n+  \"getFromDirectory\" should {\n+    def getFromDir(directory: String) = getFromDirectory(base.resolve(directory))\n+\n+    \"reject non-GET requests\" in {\n+      Put() ~> getFromDir(\"someDir\") ~> check { handled shouldEqual false }\n+    }\n+    \"reject requests to non-existing files\" in {\n+      Get(\"nonExistentFile\") ~> getFromDir(\"subDirectory\") ~> check { handled shouldEqual false }\n+    }\n+    \"reject requests to directories\" in {\n+      Get(\"sub\") ~> getFromDir(\"someDir\") ~> check { handled shouldEqual false }\n+    }\n+    \"reject path traversal attempts\" in {\n+      val route = getFromDir(\"someDir/sub\")\n+      Get(\"file.html\") ~> route ~> check { handled shouldEqual true }\n+      def shouldReject(prefix: String) = Get(prefix + \"fileA.txt\") ~> route ~> check { handled shouldEqual false }\n+      shouldReject(\"../\")","path":"akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectivesSpec.scala","position":null,"original_position":174,"commit_id":"3da948e9e72df18cf58ee493454ce820de2f3086","original_commit_id":"b25afb6953891edf86caa94a5fa87a47fba9ba50","user":{"login":"jrudolph","id":9868,"avatar_url":"https://avatars.githubusercontent.com/u/9868?v=3","gravatar_id":"","url":"https://api.github.com/users/jrudolph","html_url":"https://github.com/jrudolph","followers_url":"https://api.github.com/users/jrudolph/followers","following_url":"https://api.github.com/users/jrudolph/following{/other_user}","gists_url":"https://api.github.com/users/jrudolph/gists{/gist_id}","starred_url":"https://api.github.com/users/jrudolph/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jrudolph/subscriptions","organizations_url":"https://api.github.com/users/jrudolph/orgs","repos_url":"https://api.github.com/users/jrudolph/repos","events_url":"https://api.github.com/users/jrudolph/events{/privacy}","received_events_url":"https://api.github.com/users/jrudolph/received_events","type":"User","site_admin":false},"body":"I'll have a look.","created_at":"2016-09-26T15:57:33Z","updated_at":"2016-09-26T15:57:34Z","html_url":"https://github.com/akka/akka-http/pull/348#discussion_r80508931","pull_request_url":"https://api.github.com/repos/akka/akka-http/pulls/348","_links":{"self":{"href":"https://api.github.com/repos/akka/akka-http/pulls/comments/80508931"},"html":{"href":"https://github.com/akka/akka-http/pull/348#discussion_r80508931"},"pull_request":{"href":"https://api.github.com/repos/akka/akka-http/pulls/348"}}},"pull_request":{"url":"https://api.github.com/repos/akka/akka-http/pulls/348","id":86520365,"html_url":"https://github.com/akka/akka-http/pull/348","diff_url":"https://github.com/akka/akka-http/pull/348.diff","patch_url":"https://github.com/akka/akka-http/pull/348.patch","issue_url":"https://api.github.com/repos/akka/akka-http/issues/348","number":348,"state":"open","locked":false,"title":"DO NET MERGE: defend against directory traversal","user":{"login":"2beaucoup","id":243903,"avatar_url":"https://avatars.githubusercontent.com/u/243903?v=3","gravatar_id":"","url":"https://api.github.com/users/2beaucoup","html_url":"https://github.com/2beaucoup","followers_url":"https://api.github.com/users/2beaucoup/followers","following_url":"https://api.github.com/users/2beaucoup/following{/other_user}","gists_url":"https://api.github.com/users/2beaucoup/gists{/gist_id}","starred_url":"https://api.github.com/users/2beaucoup/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/2beaucoup/subscriptions","organizations_url":"https://api.github.com/users/2beaucoup/orgs","repos_url":"https://api.github.com/users/2beaucoup/repos","events_url":"https://api.github.com/users/2beaucoup/events{/privacy}","received_events_url":"https://api.github.com/users/2beaucoup/received_events","type":"User","site_admin":false},"body":"refs #346","created_at":"2016-09-23T14:15:41Z","updated_at":"2016-09-26T15:57:34Z","closed_at":null,"merged_at":null,"merge_commit_sha":"33e2ed355e76a1647ea9e81c96d142f22197297f","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/akka/akka-http/pulls/348/commits","review_comments_url":"https://api.github.com/repos/akka/akka-http/pulls/348/comments","review_comment_url":"https://api.github.com/repos/akka/akka-http/pulls/comments{/number}","comments_url":"https://api.github.com/repos/akka/akka-http/issues/348/comments","statuses_url":"https://api.github.com/repos/akka/akka-http/statuses/3da948e9e72df18cf58ee493454ce820de2f3086","head":{"label":"akka:2beaucoup/directory-traversal","ref":"2beaucoup/directory-traversal","sha":"3da948e9e72df18cf58ee493454ce820de2f3086","user":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"repo":{"id":66077955,"name":"akka-http","full_name":"akka/akka-http","owner":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akka/akka-http","description":"The Streaming-first HTTP server/module of Akka","fork":false,"url":"https://api.github.com/repos/akka/akka-http","forks_url":"https://api.github.com/repos/akka/akka-http/forks","keys_url":"https://api.github.com/repos/akka/akka-http/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akka/akka-http/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akka/akka-http/teams","hooks_url":"https://api.github.com/repos/akka/akka-http/hooks","issue_events_url":"https://api.github.com/repos/akka/akka-http/issues/events{/number}","events_url":"https://api.github.com/repos/akka/akka-http/events","assignees_url":"https://api.github.com/repos/akka/akka-http/assignees{/user}","branches_url":"https://api.github.com/repos/akka/akka-http/branches{/branch}","tags_url":"https://api.github.com/repos/akka/akka-http/tags","blobs_url":"https://api.github.com/repos/akka/akka-http/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akka/akka-http/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akka/akka-http/git/refs{/sha}","trees_url":"https://api.github.com/repos/akka/akka-http/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akka/akka-http/statuses/{sha}","languages_url":"https://api.github.com/repos/akka/akka-http/languages","stargazers_url":"https://api.github.com/repos/akka/akka-http/stargazers","contributors_url":"https://api.github.com/repos/akka/akka-http/contributors","subscribers_url":"https://api.github.com/repos/akka/akka-http/subscribers","subscription_url":"https://api.github.com/repos/akka/akka-http/subscription","commits_url":"https://api.github.com/repos/akka/akka-http/commits{/sha}","git_commits_url":"https://api.github.com/repos/akka/akka-http/git/commits{/sha}","comments_url":"https://api.github.com/repos/akka/akka-http/comments{/number}","issue_comment_url":"https://api.github.com/repos/akka/akka-http/issues/comments{/number}","contents_url":"https://api.github.com/repos/akka/akka-http/contents/{+path}","compare_url":"https://api.github.com/repos/akka/akka-http/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akka/akka-http/merges","archive_url":"https://api.github.com/repos/akka/akka-http/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akka/akka-http/downloads","issues_url":"https://api.github.com/repos/akka/akka-http/issues{/number}","pulls_url":"https://api.github.com/repos/akka/akka-http/pulls{/number}","milestones_url":"https://api.github.com/repos/akka/akka-http/milestones{/number}","notifications_url":"https://api.github.com/repos/akka/akka-http/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akka/akka-http/labels{/name}","releases_url":"https://api.github.com/repos/akka/akka-http/releases{/id}","deployments_url":"https://api.github.com/repos/akka/akka-http/deployments","created_at":"2016-08-19T11:33:36Z","updated_at":"2016-09-26T10:08:28Z","pushed_at":"2016-09-26T15:55:29Z","git_url":"git://github.com/akka/akka-http.git","ssh_url":"git@github.com:akka/akka-http.git","clone_url":"https://github.com/akka/akka-http.git","svn_url":"https://github.com/akka/akka-http","homepage":"http://akka.io","size":17878,"stargazers_count":59,"watchers_count":59,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":14,"mirror_url":null,"open_issues_count":266,"forks":14,"open_issues":266,"watchers":59,"default_branch":"master"}},"base":{"label":"akka:master","ref":"master","sha":"d8c6b87331604d792156d578800baf09ea5881db","user":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"repo":{"id":66077955,"name":"akka-http","full_name":"akka/akka-http","owner":{"login":"akka","id":1496237,"avatar_url":"https://avatars.githubusercontent.com/u/1496237?v=3","gravatar_id":"","url":"https://api.github.com/users/akka","html_url":"https://github.com/akka","followers_url":"https://api.github.com/users/akka/followers","following_url":"https://api.github.com/users/akka/following{/other_user}","gists_url":"https://api.github.com/users/akka/gists{/gist_id}","starred_url":"https://api.github.com/users/akka/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/akka/subscriptions","organizations_url":"https://api.github.com/users/akka/orgs","repos_url":"https://api.github.com/users/akka/repos","events_url":"https://api.github.com/users/akka/events{/privacy}","received_events_url":"https://api.github.com/users/akka/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/akka/akka-http","description":"The Streaming-first HTTP server/module of Akka","fork":false,"url":"https://api.github.com/repos/akka/akka-http","forks_url":"https://api.github.com/repos/akka/akka-http/forks","keys_url":"https://api.github.com/repos/akka/akka-http/keys{/key_id}","collaborators_url":"https://api.github.com/repos/akka/akka-http/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/akka/akka-http/teams","hooks_url":"https://api.github.com/repos/akka/akka-http/hooks","issue_events_url":"https://api.github.com/repos/akka/akka-http/issues/events{/number}","events_url":"https://api.github.com/repos/akka/akka-http/events","assignees_url":"https://api.github.com/repos/akka/akka-http/assignees{/user}","branches_url":"https://api.github.com/repos/akka/akka-http/branches{/branch}","tags_url":"https://api.github.com/repos/akka/akka-http/tags","blobs_url":"https://api.github.com/repos/akka/akka-http/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/akka/akka-http/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/akka/akka-http/git/refs{/sha}","trees_url":"https://api.github.com/repos/akka/akka-http/git/trees{/sha}","statuses_url":"https://api.github.com/repos/akka/akka-http/statuses/{sha}","languages_url":"https://api.github.com/repos/akka/akka-http/languages","stargazers_url":"https://api.github.com/repos/akka/akka-http/stargazers","contributors_url":"https://api.github.com/repos/akka/akka-http/contributors","subscribers_url":"https://api.github.com/repos/akka/akka-http/subscribers","subscription_url":"https://api.github.com/repos/akka/akka-http/subscription","commits_url":"https://api.github.com/repos/akka/akka-http/commits{/sha}","git_commits_url":"https://api.github.com/repos/akka/akka-http/git/commits{/sha}","comments_url":"https://api.github.com/repos/akka/akka-http/comments{/number}","issue_comment_url":"https://api.github.com/repos/akka/akka-http/issues/comments{/number}","contents_url":"https://api.github.com/repos/akka/akka-http/contents/{+path}","compare_url":"https://api.github.com/repos/akka/akka-http/compare/{base}...{head}","merges_url":"https://api.github.com/repos/akka/akka-http/merges","archive_url":"https://api.github.com/repos/akka/akka-http/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/akka/akka-http/downloads","issues_url":"https://api.github.com/repos/akka/akka-http/issues{/number}","pulls_url":"https://api.github.com/repos/akka/akka-http/pulls{/number}","milestones_url":"https://api.github.com/repos/akka/akka-http/milestones{/number}","notifications_url":"https://api.github.com/repos/akka/akka-http/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/akka/akka-http/labels{/name}","releases_url":"https://api.github.com/repos/akka/akka-http/releases{/id}","deployments_url":"https://api.github.com/repos/akka/akka-http/deployments","created_at":"2016-08-19T11:33:36Z","updated_at":"2016-09-26T10:08:28Z","pushed_at":"2016-09-26T15:55:29Z","git_url":"git://github.com/akka/akka-http.git","ssh_url":"git@github.com:akka/akka-http.git","clone_url":"https://github.com/akka/akka-http.git","svn_url":"https://github.com/akka/akka-http","homepage":"http://akka.io","size":17878,"stargazers_count":59,"watchers_count":59,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":14,"mirror_url":null,"open_issues_count":266,"forks":14,"open_issues":266,"watchers":59,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/akka/akka-http/pulls/348"},"html":{"href":"https://github.com/akka/akka-http/pull/348"},"issue":{"href":"https://api.github.com/repos/akka/akka-http/issues/348"},"comments":{"href":"https://api.github.com/repos/akka/akka-http/issues/348/comments"},"review_comments":{"href":"https://api.github.com/repos/akka/akka-http/pulls/348/comments"},"review_comment":{"href":"https://api.github.com/repos/akka/akka-http/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/akka/akka-http/pulls/348/commits"},"statuses":{"href":"https://api.github.com/repos/akka/akka-http/statuses/3da948e9e72df18cf58ee493454ce820de2f3086"}}}}
{"actor":{"display_login":"jrudolph"}}