Skip to content

Commit 7bf0e7a

Browse files
committed
Return status 400 on invalid file names
The multipart middleware would throw an exception when it encountered an invalid filename. For most apps that exception would be caught by the default exception handler and result in a status code of 500 instead of 400. This commit catches this specific exception and returns a 400 response from the server instead. The error response is made customizable through the optional :invalid-filename-handler.
1 parent 847cb73 commit 7bf0e7a

File tree

2 files changed

+98
-32
lines changed

2 files changed

+98
-32
lines changed

ring-core/src/ring/middleware/multipart_params.clj

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
ring.middleware.multipart-params.temp-file/temp-file-store"
1010
(:require [ring.util.codec :refer [assoc-conj]]
1111
[ring.util.request :as req]
12+
[ring.util.response :as response]
1213
[ring.util.parsing :refer [re-charset]])
1314
(:import [org.apache.commons.fileupload UploadContext
1415
FileItemIterator
1516
FileItemStream
1617
FileUpload
18+
InvalidFileNameException
1719
ProgressListener]
1820
[org.apache.commons.io IOUtils]))
1921
(defn- progress-listener
@@ -135,41 +137,73 @@
135137
{:multipart-params params}
136138
{:params params}))))
137139

140+
(defn default-invalid-filename-handler
141+
([request]
142+
(response/bad-request
143+
(str "Invalid filename in multipart upload: "
144+
(::invalid-filename request))))
145+
([request respond raise]
146+
(respond (default-invalid-filename-handler request))))
147+
148+
(defn- add-invalid-filename-to-req
149+
[request ^InvalidFileNameException invalid-filename-exception]
150+
(assoc request ::invalid-filename (.getName invalid-filename-exception)))
151+
138152
(defn wrap-multipart-params
139-
"Middleware to parse multipart parameters from a request. Adds the
140-
following keys to the request map:
153+
"Middleware to parse multipart parameters from a request. Adds the following
154+
keys to the request map:
141155
142156
:multipart-params - a map of multipart parameters
143157
:params - a merged map of all types of parameter
144158
145-
The following options are accepted
146-
147-
:encoding - character encoding to use for multipart parsing.
148-
Overrides the encoding specified in the request. If not
149-
specified, uses the encoding specified in a part named
150-
\"_charset_\", or the content type for each part, or
151-
request character encoding if the part has no encoding,
152-
or \"UTF-8\" if no request character encoding is set.
153-
154-
:fallback-encoding - specifies the character encoding used in parsing if a
155-
part of the request does not specify encoding in its
156-
content type or no part named \"_charset_\" is present.
157-
Has no effect if :encoding is also set.
158-
159-
:store - a function that stores a file upload. The function
160-
should expect a map with :filename, :content-type and
161-
:stream keys, and its return value will be used as the
162-
value for the parameter in the multipart parameter map.
163-
The default storage function is the temp-file-store.
164-
165-
:progress-fn - a function that gets called during uploads. The
166-
function should expect four parameters: request,
167-
bytes-read, content-length, and item-count."
159+
The following options are accepted:
160+
161+
:encoding
162+
- Character encoding to use for multipart parsing. Overrides the encoding
163+
specified in the request. If not specified,uses the encoding specified in a
164+
part named \"_charset_\", or the content type for each part, or request
165+
character encoding if the part has no encoding, or \"UTF-8\" if no request
166+
character encoding is set.
167+
168+
:fallback-encoding
169+
- Specifies the character encoding used in parsing if a part of the request
170+
does not specify encoding in its content type or no part named \"_charset_\"
171+
is present. Has no effect if :encoding is also set.
172+
173+
:store
174+
- A function that stores a file upload. The function should expect a map with
175+
:filename, :content-type and :stream keys, and its return value will be used
176+
as the value for the parameter in the multipart parameter map. The default
177+
storage function is the temp-file-store.
178+
179+
:progress-fn
180+
- A function that gets called during uploads. The function should expect four
181+
parameters: request, bytes-read, content-length, and item-count.
182+
183+
:invalid-filename-handler
184+
- A handler that gets called when the file being uploaded has an invalid name.
185+
When this handler receives the request it can expect one additional key,
186+
::invalid-filename, which contains the name of the invalid file."
168187
([handler]
169188
(wrap-multipart-params handler {}))
170189
([handler options]
171-
(fn
172-
([request]
173-
(handler (multipart-params-request request options)))
174-
([request respond raise]
175-
(handler (multipart-params-request request options) respond raise)))))
190+
(let [invalid-filename-handler
191+
(:invalid-filename-handler options default-invalid-filename-handler)]
192+
(fn ([request]
193+
(let [req-or-ex (try
194+
(multipart-params-request request options)
195+
(catch InvalidFileNameException ex ex))]
196+
(if (instance? InvalidFileNameException req-or-ex)
197+
(-> request
198+
(add-invalid-filename-to-req req-or-ex)
199+
invalid-filename-handler)
200+
(handler req-or-ex))))
201+
([request respond raise]
202+
(let [req-or-ex (try
203+
(multipart-params-request request options)
204+
(catch InvalidFileNameException ex ex))]
205+
(if (instance? InvalidFileNameException req-or-ex)
206+
(-> request
207+
(add-invalid-filename-to-req req-or-ex)
208+
(invalid-filename-handler respond raise))
209+
(handler req-or-ex respond raise))))))))

ring-core/test/ring/middleware/test/multipart_params.clj

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
(ns ring.middleware.test.multipart-params
22
(:require [clojure.test :refer :all]
3-
[ring.middleware.multipart-params :refer :all]
3+
[ring.middleware.multipart-params :refer :all :as multipart-params]
44
[ring.middleware.multipart-params.byte-array :refer :all]
5-
[ring.util.io :refer [string-input-stream]]))
5+
[ring.util.io :refer [string-input-stream]])
6+
(:import org.apache.commons.fileupload.InvalidFileNameException))
67

78
(defn string-store [item]
89
(-> (select-keys item [:filename :content-type])
@@ -175,3 +176,34 @@
175176
:body (string-input-stream form-body "ISO-8859-15")}
176177
request* (multipart-params-request request {:fallback-encoding "ISO-8859-15"})]
177178
(is (= (get-in request* [:multipart-params "foo"]) "äÄÖöÅå€"))))
179+
180+
(deftest wrap-multipart-params-error-test
181+
(let [invalid-filename (str "foo" \u0000 ".bar")
182+
form-body (str "--XXXX\r\n"
183+
"Content-Disposition: form-data;"
184+
"name=foo; filename=" invalid-filename "\r\n"
185+
"Content-Type: text/plain\r\n\r\n"
186+
"foo\r\n"
187+
"--XXXX--")
188+
request {:headers {"content-type"
189+
(str "multipart/form-data; boundary=XXXX; charset=US-ASCII")}
190+
:body (string-input-stream form-body "ISO-8859-1")}
191+
invalid-filename-exception
192+
(try
193+
(org.apache.commons.fileupload.util.Streams/checkFileName invalid-filename)
194+
(catch Exception e (println (.getName e)) e))
195+
err-response (default-invalid-filename-handler
196+
{::multipart-params/invalid-filename
197+
(.getName invalid-filename-exception)})]
198+
(testing "Synchronous error response"
199+
(let [handler (wrap-multipart-params identity)]
200+
(is (= err-response (handler request)))))
201+
(testing "Asynchronous error response"
202+
(let [handler (wrap-multipart-params (fn [req respond _]
203+
(respond req)))
204+
response (promise)
205+
exception (promise)
206+
request (assoc request :body (string-input-stream form-body "ISO-8859-1"))]
207+
(handler request response exception)
208+
(is (= err-response (deref response 2000 :timed-out)))
209+
(is (not (realized? exception)))))))

0 commit comments

Comments
 (0)