Repository: Pylons/webob

Issue:
multipart field names may be None
I'm getting this stack trace.

```
Traceback (most recent call last):
  File "/home/bukzor/my/wsgi/app.py", line 11, in handler
    request = new_request(environ).decode('latin1')
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 243, in decode
    fout = t.transcode_fs(fs, r._content_type_raw)
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 1699, in transcode_fs
    field.name = decode(field.name)
  File "/usr/lib/pymodules/python2.6/webob/request.py", line 1696, in <lambda>
    decode = lambda b: b.decode(self.charset, self.errors)
AttributeError: 'NoneType' object has no attribute 'decode'
```

The docstring for fieldStorage notes that "None can occur as a field name". webob should allow for this.

You can use this little client to reproduce the issue:

``` python
#!/usr/bin/env python                                                                                                                                                                                        [0/1878]
import socket                                                                                                                                                                                                        
HOST = 'www.buck.dev.yelp.com'                                                                                                                                                                                       
PATH = '/lil_brudder'                                                                                                                                                                                                
PORT = 80                                                                                                                                                                                                            

body = '''\                                                                                                                                                                                                          
--FOO                                                                                                                                                                                                                

123                                                                                                                                                                                                                  
--FOO--'''                                                                                                                                                                                                           

req = '''\                                                                                                                                                                                                           
POST %s HTTP/1.0                                                                                                                                                                                                     
Host: %s                                                                                                                                                                                                             
Content-Length: %s                                                                                                                                                                                                   
Content-Type: multipart/form-data; boundary=FOO                                                                                                                                                                      

%s                                                                                                                                                                                                                   
''' % (PATH, HOST, len(body), body)                                                                                                                                                                                  

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)                                                                                                                                                             
sock.connect((HOST, PORT))                                                                                                                                                                                           
sock.send(req)                                                                                                                                                                                                       
sock.close()                                                       
```


Comments:

Patch:
diff --git a/docs/do-it-yourself.txt b/docs/do-it-yourself.txt
index 3b65c7d..e88bbab 100644
--- a/docs/do-it-yourself.txt
+++ b/docs/do-it-yourself.txt
@@ -295,7 +295,7 @@ To do that we'll write a `decorator <http://www.ddj.com/web-development/18440607
 
 **line 6**: Here we catch any ``webob.exc.HTTPException`` exceptions.  This is so you can do ``raise webob.exc.HTTPNotFound()`` in your function.  These exceptions are themselves WSGI applications.
 
-**line 7**: We call the function with the request object, any any variables in ``req.urlvars``.  And we get back a response.
+**line 7**: We call the function with the request object, any variables in ``req.urlvars``.  And we get back a response.
 
 **line 10**: We'll allow the function to return a full response object, or just a string.  If they return a string, we'll create a ``Response`` object with that (and with the standard ``200 OK`` status, ``text/html`` content type, and ``utf8`` charset/encoding).
 
diff --git a/webob/request.py b/webob/request.py
index 150dd37..923cce5 100644
--- a/webob/request.py
+++ b/webob/request.py
@@ -1629,8 +1629,9 @@ def _encode_multipart(vars, content_type, fout=None):
         w(b'--')
         wt(boundary)
         w(CRLF)
-        assert name is not None, 'Value associated with no name: %r' % value
-        wt('Content-Disposition: form-data; name="%s"' % name)
+        wt('Content-Disposition: form-data')
+        if name is not None:
+            wt('; name="%s"' % name)
         filename = None
         if getattr(value, 'filename', None):
             filename = value.filename
@@ -1708,7 +1709,10 @@ class Transcoder(object):
         # transcode FieldStorage
         if PY2:
             def decode(b):
-                return b.decode(self.charset, self.errors)
+                if b is not None:
+                    return b.decode(self.charset, self.errors)
+                else:
+                    return b
         else:
             def decode(b):
                 return b


Test patch:
diff --git a/tests/test_request.py b/tests/test_request.py
index de3b149..357a724 100644
--- a/tests/test_request.py
+++ b/tests/test_request.py
@@ -2618,6 +2618,20 @@ class TestRequest_functional(object):
         req2 = req2.decode('latin-1')
         assert body == req2.body
 
+    def test_none_field_name(self):
+        from webob.request import Request
+        body = b'--FOO\r\nContent-Disposition: form-data\r\n\r\n123\r\n--FOO--'
+        content_type = 'multipart/form-data; boundary=FOO'
+        environ = {
+            'wsgi.input': BytesIO(body),
+            'CONTENT_TYPE': content_type,
+            'CONTENT_LENGTH': len(body),
+            'REQUEST_METHOD': 'POST'
+        }
+        req = Request(environ)
+        req = req.decode('latin-1')
+        assert body == req.body
+
     def test_broken_seek(self):
         # copy() should work even when the input has a broken seek method
         req = self._blankOne('/', method='POST',