Repository: rack/rack (Ruby)

Issue:
Robust multipart field separation
As a maintainer of the [Pharo](https://pharo.org) track of the [Exercism](https://exercism.io) project, we wrote our own IDE-integrated-client to interface the Exercism REST API.  IIUC, Exercism uses Sinatra which uses Rack which has led me here.  I'm not a Ruby programmer and never used Sinatra or Rack before, so please bear with me as I try to provide anything you need from me.

One of the API features is multipart uploading of files.  Out of the box this was broken with the standard Pharo library due to lack of a space between multipart fields.  Specifically I discovered that the Pharo library sent... 
```
Content-Disposition: form-data;name="files[]";filename="myfile.txt"
```
which failed with **"500 INTERNAL SERVER ERROR"**, 
while Exercism's default client (i.e. library of another language) worked sending...
```
Content-Disposition: form-data; name="files[]"; filename="myfile.txt"
```
with the difference being the space after the semi-colon between parameters.

For our own application we have already solved this by monkey-patching the Pharo library, and am following up other channels to change the Pharo library, but its a subtle error (especially wrapped by SSL) and discovery took about 30 hours focused investigation.  So I'm seeking to spare future-others that trouble. 

First, a side-query... *Is it a "requirement" of an RFC somewhere the there "MUST" be a space between multipart fields?*  But in any case, even if it "must" have a space, the [Robustness Principal](https://en.wikipedia.org/wiki/Robustness_principle) suggests parsing input sans-space would be useful.


Discussion:
Here is some data from the original investigation... https://www.diffchecker.com/ETIfCUzx
Top half is some Wireshark capture.  Bottom half is the Pharo library code.
Taking a guess at a first test, 
in ".../rack/test/multipart/" I copied "**content_type_and_no_filename**" to "**robust_field_separation**" 
and made this diff...
```
2c2
< Content-Disposition: form-data; name="text"
---
> Content-Disposition: form-data;name="text"
```
then duplicated ["parse multipart content when content type present but filename is not"](https://github.com/rack/rack/blob/master/test/spec_multipart.rb#L33-L38) 
modified as follows...
```
  it "be robust separating fields sans-space" do
    env = Rack::MockRequest.env_for("/", multipart_fixture(:robust_field_separation))
    params = Rack::Multipart.parse_multipart(env)
    params["text"].must_equal "contents"
  end
```
Maybe additional fields need broader testing.
From https://github.com/rack/rack/blob/master/lib/rack/multipart.rb#L16-L22
expanding these...
```
    TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
    VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
    MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
```
I get... `Content-Disposition:.*\s+name=("(?:\\"|[^"])*"|[^\s()<>,;:\\"\/\[\]?=]+)`

At https://regex101.com/, testing a change from a "\s+" to "\s*"   I get...
![image](https://user-images.githubusercontent.com/1713447/63567023-10476900-c5a2-11e9-9577-6287b41c0a5d.png)

Making that modification changes my "be robust separating fields sans-space" test
from FAIL to OKAY, but breaks other tests that I don't have the skills to debug in Ruby.
As a reference, [`mime.ParseMediaType`](http://golang.org/pkg/mime/#ParseMediaType) in Go handles `form-data;name="files[]";filename="myfile.txt"`: https://play.golang.org/p/AbZU7iVp1RB
lgtm.
Can you make a PR and I will review.
apologies, this fell off my list.  
thx @fatkodima 



Patch:
diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb
index 31ac29ebb..bd91f43f4 100644
--- a/lib/rack/multipart.rb
+++ b/lib/rack/multipart.rb
@@ -16,10 +16,10 @@ module Multipart
     TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
     CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
     VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
-    BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
-    BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
+    BROKEN_QUOTED = /^#{CONDISP}.*;\s*filename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
+    BROKEN_UNQUOTED = /^#{CONDISP}.*;\s*filename=(#{TOKEN})/i
     MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
-    MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name=(#{VALUE})/ni
+    MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*;\s*name=(#{VALUE})/ni
     MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
     # Updated definitions from RFC 2231
     ATTRIBUTE_CHAR = %r{[^ \t\v\n\r)(><@,;:\\"/\[\]?='*%]}


Test patch:
diff --git a/test/multipart/robust_field_separation b/test/multipart/robust_field_separation
new file mode 100644
index 000000000..34956b150
--- /dev/null
+++ b/test/multipart/robust_field_separation
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data;name="text"
+Content-Type: text/plain
+
+contents
+--AaB03x--
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index b029048e3..2d51f0915 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -306,6 +306,12 @@ def initialize(*)
     params["files"][:filename].must_equal "flowers.exe\u0000.jpg"
   end
 
+  it "is robust separating Content-Disposition fields" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:robust_field_separation))
+    params = Rack::Multipart.parse_multipart(env)
+    params["text"].must_equal "contents"
+  end
+
   it "not include file params if no file was selected" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
     params = Rack::Multipart.parse_multipart(env)