From efeb62c103c7e2a6774778cd2fb616cf5d6aa83d Mon Sep 17 00:00:00 2001 From: "Kevin J. Lynagh" Date: Sat, 7 Apr 2012 10:14:50 -0700 Subject: [PATCH] Add :allow-symlink? option to wrap-file middleware (defaults to false). --- ring-core/src/ring/middleware/file.clj | 2 +- ring-core/src/ring/util/response.clj | 16 +++++++++++++--- ring-core/test/ring/assets/bars/backlink | 1 + ring-core/test/ring/util/test/response.clj | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 120000 ring-core/test/ring/assets/bars/backlink diff --git a/ring-core/src/ring/middleware/file.clj b/ring-core/src/ring/middleware/file.clj index 6139bb6..fb9d95e 100644 --- a/ring-core/src/ring/middleware/file.clj +++ b/ring-core/src/ring/middleware/file.clj @@ -20,7 +20,7 @@ to the ring.util.response/file-response function." [app ^String root-path & [opts]] (ensure-dir root-path) - (let [opts (merge {:root root-path, :index-files? true} opts)] + (let [opts (merge {:root root-path, :index-files? true, :allow-symlinks? false} opts)] (fn [req] (if-not (= :get (:request-method req)) (app req) diff --git a/ring-core/src/ring/util/response.clj b/ring-core/src/ring/util/response.clj index 0f727a5..3ab4e86 100644 --- a/ring-core/src/ring/util/response.clj +++ b/ring-core/src/ring/util/response.clj @@ -39,6 +39,13 @@ (.startsWith (.getCanonicalPath (File. root path)) (.getCanonicalPath (File. root)))) +(defn- directory-transversal? + "Check if a path contains '..'." + [^String path] + (-> (str/split path #"/|\\") + (set) + (contains? ".."))) + (defn- find-index-file "Search the directory for an index file." [^File dir] @@ -52,7 +59,9 @@ explanation of options." [^String path opts] (if-let [^File file (if-let [^String root (:root opts)] - (and (safe-path? root path) (File. root path)) + (if (or (safe-path? root path) + (and (:allow-symlinks? opts) (not (directory-transversal? path)))) + (File. root path)) (File. path))] (cond (.isDirectory file) @@ -64,8 +73,9 @@ "Returns a Ring response to serve a static file, or nil if an appropriate file does not exist. Options: - :root - take the filepath relative to this root path - :index-files? - look for index.* files in directories, defaults to true" + :root - take the filepath relative to this root path + :index-files? - look for index.* files in directories, defaults to true + :allow-symlinks? - serve files through symbolic links, defaults to false" [filepath & [opts]] (if-let [file (get-file filepath opts)] (response file))) diff --git a/ring-core/test/ring/assets/bars/backlink b/ring-core/test/ring/assets/bars/backlink new file mode 120000 index 0000000..b870225 --- /dev/null +++ b/ring-core/test/ring/assets/bars/backlink @@ -0,0 +1 @@ +../ \ No newline at end of file diff --git a/ring-core/test/ring/util/test/response.clj b/ring-core/test/ring/util/test/response.clj index 8ebfdbf..72cd7ec 100644 --- a/ring-core/test/ring/util/test/response.clj +++ b/ring-core/test/ring/util/test/response.clj @@ -104,6 +104,25 @@ (is (= (slurp (:body resp)) "Hello World\n"))))) +(deftest test-file-response + (testing "response map" + (let [resp (file-response "foo.html" {:root "test/ring/assets"})] + (is (= (resp :status) 200)) + (is (= (resp :headers) {})) + (is (= (slurp (resp :body)) "foo")))) + + (testing "file path cannot contain '..' " + (is (nil? (file-response "../../../project.clj" {:root "test/ring/assets"}))) + (is (nil? (file-response "../../../project.clj" {:root "test/ring/assets/bars" :allow-symlinks? true})))) + + (testing "file response optionally follows symlinks" + (let [resp (file-response "backlink/foo.html" {:root "test/ring/assets/bars" :allow-symlinks? true})] + (is (= (resp :status) 200)) + (is (= (resp :headers) {})) + (is (= (slurp (resp :body)) "foo"))) + + (is (nil? (file-response "backlink/foo.html" {:root "test/ring/assets/bars"}))))) + (deftest test-set-cookie (is (= {:status 200 :headers {} :cookies {"Foo" {:value "Bar"}}} (set-cookie {:status 200 :headers {}}