From ba6e88200bb526eb17d8c7a26816ac16afac7bf1 Mon Sep 17 00:00:00 2001 From: Craig McDaniel Date: Fri, 28 Feb 2014 08:26:36 -0500 Subject: [PATCH] Partial implementation of fix for :clean-targets sanity checking (issue #1458) --- src/leiningen/clean.clj | 41 +++++++++++++++++++++++++++++++---- test/leiningen/test/clean.clj | 24 +++++++++++++------- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/src/leiningen/clean.clj b/src/leiningen/clean.clj index c84f2f18d..cc4a4a223 100644 --- a/src/leiningen/clean.clj +++ b/src/leiningen/clean.clj @@ -2,7 +2,8 @@ "Remove all files from project's target-path." (:require [clojure.java.io :as io] [leiningen.core.eval :as eval] - [leiningen.core.utils :as utils])) + [leiningen.core.utils :as utils]) + (:import [java.io IOException])) (defn real-directory? "Returns true if this file is a real directory, false if it is a symlink or a @@ -24,6 +25,40 @@ Raise an exception if any deletion fails unless silently is true." (.setWritable f true) (io/delete-file f silently))) +(defn ancestor? + "Is a and ancestor of b?" + [a b] + (let [hypothetical-ancestor (.getCanonicalPath (io/file a)) + hypothetical-descendant (.getCanonicalPath (io/file b))] + (and (.startsWith hypothetical-descendant hypothetical-ancestor) + (not (= hypothetical-descendant hypothetical-ancestor))))) + +(defn protected-paths + "Returns a set of leiningen project source directories and important files." + [project] + (->> [:source-paths :java-source-paths :test-paths :resource-paths] + (select-keys project) + vals + flatten + (cons "doc") + (cons "project.clj") + (map io/file) + set)) + +(defn protected-path? + "Is dir one of the leiningen project directories (which we expect to be version controlled)?" + [project dir] + ((protected-paths project) (io/file dir))) + +(defn sanity-check + "Ensure that a clean-target string refers to a directory that is sensible to delete." + [project clean-target] + (when (string? clean-target) + (cond (not (ancestor? (:root project) clean-target)) + (throw (IOException. "Deleting the project root directory or its ancestors is not allowed.")) + (protected-path? project clean-target) + (throw (IOException. "Deleting non-target project directories is not allowed."))))) + (defn clean "Remove all files from paths in project's clean-targets." [project] @@ -32,7 +67,5 @@ Raise an exception if any deletion fails unless silently is true." (keyword? target-key) (target-key project) (string? target-key) target-key)] (doseq [f (flatten [target])] + (sanity-check project f) (delete-file-recursively f :silently))))) - - - diff --git a/test/leiningen/test/clean.clj b/test/leiningen/test/clean.clj index e69d0bca8..93b8b6fdc 100644 --- a/test/leiningen/test/clean.clj +++ b/test/leiningen/test/clean.clj @@ -20,7 +20,7 @@ (.createNewFile (file target-dir "foo.tmp"))) (f))) -;; TODO test explicit :clean-targets with ancestor string path "..","/" +;; TODO test explicit :clean-targets with ancestor string path "../xyz","/" ;; TODO test explicit :clean-targets with src dir string paths. (deftest test-default-clean-target @@ -54,21 +54,29 @@ (is (not (.exists (file target-3)))))) (comment - ;; this test is not yet safe to run + ;; this test may not safe to run + ;; screw this up and you may be deleting important files on your system (deftest test-explicit-clean-targets-with-invalid-string-paths - (testing "ancestor paths of the project root" - (doseq [ancestor ["../../xyz" "/xyz"]] + (testing "ancestor paths of the project root and project dirs" + (doseq [ancestor ["../../xyz" "/xyz" + "src" "test" "doc" "resources"]] (let [modified-project (assoc sample-project :clean-targets [ancestor])] (is (thrown? java.io.IOException) (clean modified-project)) - (is (.exists (file target-1)))))) + (is (.exists (file ancestor)))))) - (testing "standard lein project dir paths (non-target)" - ))) + (sort (keys sample-project)) + (->> [:source-paths :java-source-paths :test-paths :resource-paths] + (select-keys sample-project) + vals + flatten + (map file) + set) + )) -#_(run-tests) +#_(run-tests)