Permalink
Please sign in to comment.
Showing
with
175 additions
and 70 deletions.
- +57 −39 ring-core/src/ring/middleware/multipart_params.clj
- +8 −0 ring-core/src/ring/middleware/multipart_params/byte_array.clj
- +42 −0 ring-core/src/ring/middleware/multipart_params/temp_file.clj
- +13 −0 ring-core/test/ring/middleware/multipart_params/byte_array_test.clj
- +27 −0 ring-core/test/ring/middleware/multipart_params/temp_file_test.clj
- +28 −31 ring-core/test/ring/middleware/multipart_params_test.clj
96
ring-core/src/ring/middleware/multipart_params.clj
8
ring-core/src/ring/middleware/multipart_params/byte_array.clj
| @@ -0,0 +1,8 @@ | ||
| +(ns ring.middleware.multipart-params.byte-array | ||
| + (:import org.apache.commons.io.IOUtils)) | ||
| + | ||
| +(defn byte-array-store | ||
| + "Stores multipart file parameters as an array of bytes." | ||
| + [item] | ||
| + (-> (select-keys item [:filename :content-type]) | ||
| + (assoc :bytes (IOUtils/toByteArray (:stream item))))) |
42
ring-core/src/ring/middleware/multipart_params/temp_file.clj
| @@ -0,0 +1,42 @@ | ||
| +(ns ring.middleware.multipart-params.temp-file | ||
| + (:require [clojure.java.io :as io]) | ||
| + (:import java.io.File)) | ||
| + | ||
| +(defmacro ^{:private true} do-every [delay & body] | ||
| + `(future | ||
| + (while true | ||
| + (Thread/sleep (* ~delay 1000)) | ||
| + (try ~@body | ||
| + (catch Exception ex#))))) | ||
| + | ||
| +(defn- expired? [file expiry-time] | ||
| + (< (.lastModified file) | ||
| + (- (System/currentTimeMillis) | ||
| + (* expiry-time 1000)))) | ||
| + | ||
| +(defn- remove-old-files [file-set expiry-time] | ||
| + (doseq [file @file-set] | ||
| + (when (expired? file expiry-time) | ||
| + (.delete file) | ||
| + (swap! file-set disj file)))) | ||
| + | ||
| +(defn- make-temp-file [file-set] | ||
| + (let [temp-file (File/createTempFile "ring-multipart-" nil)] | ||
| + (swap! file-set conj temp-file) | ||
| + (.deleteOnExit temp-file) | ||
| + temp-file)) | ||
| + | ||
| +(defn temp-file-store | ||
| + "Stores multipart file parameters as a temporary file." | ||
| + ([] (temp-file-store {:expire-in 3600})) | ||
| + ([{:keys [expires-in]}] | ||
| + (fn [item] | ||
| + (let [file-set (atom #{}) | ||
| + temp-file (make-temp-file file-set)] | ||
| + (io/copy (:stream item) temp-file) | ||
| + (when expires-in | ||
| + (do-every expires-in | ||
| + (remove-old-files file-set expires-in))) | ||
| + (-> (select-keys item [:filename :content-type]) | ||
| + (assoc :tempfile temp-file | ||
| + :size (.length temp-file))))))) |
13
ring-core/test/ring/middleware/multipart_params/byte_array_test.clj
| @@ -0,0 +1,13 @@ | ||
| +(ns ring.middleware.multipart-params.byte-array-test | ||
| + (:use clojure.test | ||
| + ring.util.test | ||
| + ring.middleware.multipart-params.byte-array)) | ||
| + | ||
| +(deftest test-byte-array-store | ||
| + (let [result (byte-array-store | ||
| + {:filename "foo.txt" | ||
| + :content-type "text/plain" | ||
| + :stream (string-input-stream "foo")})] | ||
| + (is (= (:filename result) "foo.txt")) | ||
| + (is (= (:content-type result) "text/plain")) | ||
| + (is (= (String. (:bytes result)) "foo")))) |
27
ring-core/test/ring/middleware/multipart_params/temp_file_test.clj
| @@ -0,0 +1,27 @@ | ||
| +(ns ring.middleware.multipart-params.temp-file-test | ||
| + (:use clojure.test | ||
| + ring.util.test | ||
| + ring.middleware.multipart-params.temp-file)) | ||
| + | ||
| +(deftest test-temp-file-store | ||
| + (let [store (temp-file-store) | ||
| + result (store | ||
| + {:filename "foo.txt" | ||
| + :content-type "text/plain" | ||
| + :stream (string-input-stream "foo")})] | ||
| + (is (= (:filename result) "foo.txt")) | ||
| + (is (= (:content-type result) "text/plain")) | ||
| + (is (= (:size result) 3)) | ||
| + (is (instance? java.io.File (:tempfile result))) | ||
| + (is (.exists (:tempfile result))) | ||
| + (is (= (slurp (:tempfile result)) "foo")))) | ||
| + | ||
| +(deftest test-temp-file-expiry | ||
| + (let [store (temp-file-store {:expires-in 2}) | ||
| + result (store | ||
| + {:filename "foo.txt" | ||
| + :content-type "text/plain" | ||
| + :stream (string-input-stream "foo")})] | ||
| + (is (.exists (:tempfile result))) | ||
| + (Thread/sleep 2500) | ||
| + (is (not (.exists (:tempfile result)))))) |
59
ring-core/test/ring/middleware/multipart_params_test.clj
| @@ -1,36 +1,33 @@ | ||
| (ns ring.middleware.multipart-params-test | ||
| (:use clojure.test | ||
| - ring.middleware.multipart-params) | ||
| - (:require [ring.util.test :as tu]) | ||
| - (:import java.io.File)) | ||
| + ring.middleware.multipart-params | ||
| + ring.util.test) | ||
| + (:import java.io.InputStream)) | ||
| -(def ^{:private true} | ||
| - upload-content-type | ||
| - "multipart/form-data; boundary=----WebKitFormBoundaryAyGUY6aMxOI6UF5s") | ||
| - | ||
| -(def ^{:private true} | ||
| - upload-content-length | ||
| - 188) | ||
| - | ||
| -(def ^{:private true} | ||
| - upload-body | ||
| - (tu/string-input-stream | ||
| - "------WebKitFormBoundaryAyGUY6aMxOI6UF5s\r\nContent-Disposition: form-data; name=\"upload\"; filename=\"test.txt\"\r\nContent-Type: text/plain\r\n\r\nfoo\r\n\r\n------WebKitFormBoundaryAyGUY6aMxOI6UF5s--")) | ||
| - | ||
| -(def ^{:private true} | ||
| - wrapped-echo | ||
| - (wrap-multipart-params identity)) | ||
| +(defn string-store [item] | ||
| + (-> (select-keys item [:filename :content-type]) | ||
| + (assoc :content (slurp (:stream item))))) | ||
| (deftest test-wrap-multipart-params | ||
| - (let [req {:content-type upload-content-type | ||
| - :content-length upload-content-length | ||
| - :body upload-body | ||
| - :params {"foo" "bar"}} | ||
| - resp (wrapped-echo req)] | ||
| - (is (= "bar" (get-in resp [:params "foo"]))) | ||
| - (let [upload (get-in resp [:params "upload"])] | ||
| - (is (= "test.txt" (:filename upload))) | ||
| - (is (= 5 (:size upload))) | ||
| - (is (= "text/plain" (:content-type upload))) | ||
| - (is (instance? File (:tempfile upload))) | ||
| - (is (= "foo\r\n" (slurp (:tempfile upload))))))) | ||
| + (let [form-body (str "--XXXX\r\n" | ||
| + "Content-Disposition: form-data;" | ||
| + "name=\"upload\"; filename=\"test.txt\"\r\n" | ||
| + "Content-Type: text/plain\r\n\r\n" | ||
| + "foo\r\n" | ||
| + "--XXXX\r\n" | ||
| + "Content-Disposition: form-data;" | ||
| + "name=\"baz\"\r\n\r\n" | ||
| + "qux\r\n" | ||
| + "--XXXX--") | ||
| + handler (wrap-multipart-params identity {:store string-store}) | ||
| + request {:content-type "multipart/form-data; boundary=XXXX" | ||
| + :content-length (count form-body) | ||
| + :params {"foo" "bar"} | ||
| + :body (string-input-stream form-body)} | ||
| + response (handler request)] | ||
| + (is (= (get-in response [:params "foo"]) "bar")) | ||
| + (is (= (get-in response [:params "baz"]) "qux")) | ||
| + (let [upload (get-in response [:params "upload"])] | ||
| + (is (= (:filename upload) "test.txt")) | ||
| + (is (= (:content-type upload) "text/plain")) | ||
| + (is (= (:content upload) "foo"))))) |
0 comments on commit
a94d5f7