Index: Lib/cgi.py =================================================================== --- Lib/cgi.py (revision 71050) +++ Lib/cgi.py (working copy) @@ -41,6 +41,8 @@ import UserDict import urlparse +import email.header + from warnings import filterwarnings, catch_warnings, warn with catch_warnings(): if sys.py3kwarning: @@ -147,7 +149,7 @@ if not 'REQUEST_METHOD' in environ: environ['REQUEST_METHOD'] = 'GET' # For testing stand-alone if environ['REQUEST_METHOD'] == 'POST': - ctype, pdict = parse_header(environ['CONTENT_TYPE']) + ctype, pdict = email.header.parse_header(environ['CONTENT_TYPE']) if ctype == 'multipart/form-data': return parse_multipart(fp, pdict) elif ctype == 'application/x-www-form-urlencoded': @@ -274,7 +276,7 @@ line = headers['content-disposition'] if not line: continue - key, params = parse_header(line) + key, params = email.header.parse_header(line) if key != 'form-data': continue if 'name' in params: @@ -289,37 +291,15 @@ return partdict -def _parseparam(s): - while s[:1] == ';': - s = s[1:] - end = s.find(';') - while end > 0 and s.count('"', 0, end) % 2: - end = s.find(';', end + 1) - if end < 0: - end = len(s) - f = s[:end] - yield f.strip() - s = s[end:] - def parse_header(line): """Parse a Content-type like header. Return the main content-type and a dictionary of options. """ - parts = _parseparam(';' + line) - key = parts.next() - pdict = {} - for p in parts: - i = p.find('=') - if i >= 0: - name = p[:i].strip().lower() - value = p[i+1:].strip() - if len(value) >= 2 and value[0] == value[-1] == '"': - value = value[1:-1] - value = value.replace('\\\\', '\\').replace('\\"', '"') - pdict[name] = value - return key, pdict + warn("cgi.parse_header is deprecated, use email.header.parse_header" + " instead", PendingDeprecationWarning) + return email.header.parse_header(line) # Classes for field storage @@ -457,7 +437,7 @@ # Process content-disposition header cdisp, pdict = "", {} if 'content-disposition' in self.headers: - cdisp, pdict = parse_header(self.headers['content-disposition']) + cdisp, pdict = email.header.parse_header(self.headers['content-disposition']) self.disposition = cdisp self.disposition_options = pdict self.name = None @@ -480,7 +460,7 @@ # See below for what we do if there does exist a content-type header, # but it happens to be something we don't understand. if 'content-type' in self.headers: - ctype, pdict = parse_header(self.headers['content-type']) + ctype, pdict = email.header.parse_header(self.headers['content-type']) elif self.outerboundary or method != 'POST': ctype, pdict = "text/plain", {} else: Index: Lib/email/header.py =================================================================== --- Lib/email/header.py (revision 71050) +++ Lib/email/header.py (working copy) @@ -8,6 +8,7 @@ 'Header', 'decode_header', 'make_header', + 'parse_header', ] import re @@ -132,7 +133,42 @@ h.append(s, charset) return h +# parse_header function moved from cgi module. +# addressing issue3609 +def _parseparam(s): + while s[:1] == ';': + s = s[1:] + end = s.find(';') + while end > 0 and s.count('"', 0, end) % 2: + end = s.find(';', end + 1) + if end < 0: + end = len(s) + f = s[:end] + yield f.strip() + s = s[end:] + +def parse_header(line): + """Parse a Content-type like header. + + Return the main content-type and a dictionary of options. + + """ + parts = _parseparam(';' + line) + key = parts.next() + pdict = {} + for p in parts: + i = p.find('=') + if i >= 0: + name = p[:i].strip().lower() + value = p[i+1:].strip() + if len(value) >= 2 and value[0] == value[-1] == '"': + value = value[1:-1] + value = value.replace('\\\\', '\\').replace('\\"', '"') + pdict[name] = value + return key, pdict + + class Header: def __init__(self, s=None, charset=None, Index: Lib/email/test/test_email.py =================================================================== --- Lib/email/test/test_email.py (revision 71050) +++ Lib/email/test/test_email.py (working copy) @@ -14,7 +14,7 @@ import email from email.Charset import Charset -from email.Header import Header, decode_header, make_header +from email.Header import Header, decode_header, make_header, parse_header from email.Parser import Parser, HeaderParser from email.Generator import Generator, DecodedGenerator from email.Message import Message @@ -1561,6 +1561,42 @@ ('sbord', None)]) +class TestParseHeader(unittest.TestCase): + + def test_parse_header(self): + self.assertEqual( + parse_header("text/plain"), + ("text/plain", {})) + self.assertEqual( + parse_header("text/vnd.just.made.this.up ; "), + ("text/vnd.just.made.this.up", {})) + self.assertEqual( + parse_header("text/plain;charset=us-ascii"), + ("text/plain", {"charset": "us-ascii"})) + self.assertEqual( + parse_header('text/plain ; charset="us-ascii"'), + ("text/plain", {"charset": "us-ascii"})) + self.assertEqual( + parse_header('text/plain ; charset="us-ascii"; another=opt'), + ("text/plain", {"charset": "us-ascii", "another": "opt"})) + self.assertEqual( + parse_header('attachment; filename="silly.txt"'), + ("attachment", {"filename": "silly.txt"})) + self.assertEqual( + parse_header('attachment; filename="strange;name"'), + ("attachment", {"filename": "strange;name"})) + self.assertEqual( + parse_header('attachment; filename="strange;name";size=123;'), + ("attachment", {"filename": "strange;name", "size": "123"})) + self.assertEqual( + parse_header('attachment/X-Foo; filename="strange<;>name";size=123;'), + ("attachment/X-Foo", {"filename": "strange<;>name", "size": "123"})) + self.assertEqual( + parse_header('attachment/x-bar; filename="strange@name";size=123;'), + ("attachment/x-bar", {"filename": "strange@name", "size": "123"})) + self.assertEqual( + parse_header('attachment/X-BaR; filename="(strangename)";size=123;'), + ("attachment/X-BaR", {"filename": "(strangename)", "size": "123"})) # Test the MIMEMessage class class TestMIMEMessage(TestEmailBase): Index: Lib/test/test_cgi.py =================================================================== --- Lib/test/test_cgi.py (revision 71050) +++ Lib/test/test_cgi.py (working copy) @@ -354,7 +354,7 @@ self.assertEqual([('a', 'A1'), ('b', 'B2'), ('B', 'B3')], cgi.parse_qsl('a=A1&b=B2&B=B3')) - def test_parse_header(self): + def test_deprecated_parse_header(self): self.assertEqual( cgi.parse_header("text/plain"), ("text/plain", {}))