Update MIME type parser
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 23 Jan 2019 11:32:55 +0000 (11:32 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 23 Jan 2019 11:32:55 +0000 (11:32 +0000)
https://bugs.webkit.org/show_bug.cgi?id=180526

Patch by Rob Buis <rbuis@igalia.com> on 2019-01-23
Reviewed by Frédéric Wang.

LayoutTests/imported/w3c:

Update improved test expectations.

* web-platform-tests/xhr/overridemimetype-blob-expected.txt:

Source/WebCore:

Add an enum to allow two modes of MIME type parsing, one mode
to keep supporting RFC2045 as before, and one mode to support
the updated MIME parser from mimesniff [1]. Mimesniff support
brings the following changes:
- allows parameter names without matching =value.
- skips whitespace after subtype, parameter value and before
  parameter name.
- lower cases MIME type and parameter name.
- parameter names parsed before are discarded.

The old mode is still used by CDM.cpp and MIMEHeader.cpp.

[1] https://mimesniff.spec.whatwg.org/

* Modules/encryptedmedia/CDM.cpp:
(WebCore::CDM::getSupportedCapabilitiesForAudioVideoType):
* platform/network/MIMEHeader.cpp:
(WebCore::MIMEHeader::parseHeader):
* platform/network/ParsedContentType.cpp:
(WebCore::DummyParsedContentType::setContentType const):
(WebCore::DummyParsedContentType::setContentTypeParameter const):
(WebCore::isQuotedStringTokenCharacter):
(WebCore::isTokenCharacter):
(WebCore::parseToken):
(WebCore::containsNonTokenCharacters):
(WebCore::parseQuotedString):
(WebCore::isNotForwardSlash):
(WebCore::isNotSemicolon):
(WebCore::isNotSemicolonOrEqualSign):
(WebCore::parseContentType):
(WebCore::isValidContentType):
(WebCore::ParsedContentType::ParsedContentType):
(WebCore::ParsedContentType::setContentType):
(WebCore::isNonTokenCharacter):
(WebCore::isNonQuotedStringTokenCharacter):
(WebCore::ParsedContentType::setContentTypeParameter):
* platform/network/ParsedContentType.h:

Test: web-platform-tests/xhr/overridemimetype-blob.html

Tools:

Add unit tests for both parse modes of ParsedContentType.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebCore/ParsedContentType.cpp: Added.
(TestWebKitAPI::TEST):

LayoutTests:

Adjust test expectation.

* http/tests/xmlhttprequest/post-blob-content-type-async-expected.txt:
* http/tests/xmlhttprequest/post-blob-content-type-sync-expected.txt:
* http/tests/xmlhttprequest/post-blob-content-type-tests.js:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@240331 268f45cc-cd09-0410-ab3c-d52691b4dbfc

14 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/xmlhttprequest/post-blob-content-type-async-expected.txt
LayoutTests/http/tests/xmlhttprequest/post-blob-content-type-sync-expected.txt
LayoutTests/http/tests/xmlhttprequest/post-blob-content-type-tests.js
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/xhr/overridemimetype-blob-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/Modules/encryptedmedia/CDM.cpp
Source/WebCore/platform/network/MIMEHeader.cpp
Source/WebCore/platform/network/ParsedContentType.cpp
Source/WebCore/platform/network/ParsedContentType.h
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebCore/ParsedContentType.cpp [new file with mode: 0644]

index 0484ee9..52a8616 100644 (file)
@@ -1,3 +1,16 @@
+2019-01-23  Rob Buis  <rbuis@igalia.com>
+
+        Update MIME type parser
+        https://bugs.webkit.org/show_bug.cgi?id=180526
+
+        Reviewed by Frédéric Wang.
+
+        Adjust test expectation.
+
+        * http/tests/xmlhttprequest/post-blob-content-type-async-expected.txt:
+        * http/tests/xmlhttprequest/post-blob-content-type-sync-expected.txt:
+        * http/tests/xmlhttprequest/post-blob-content-type-tests.js:
+
 2019-01-22  Simon Fraser  <simon.fraser@apple.com>
 
         Adding a child to a ScrollingStateNode needs to trigger a tree state commit
index 8809c11..5103f19 100644 (file)
@@ -12,7 +12,7 @@ PASS expectedMimeType is ""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
-PASS expectedMimeType is ""
+PASS expectedMimeType is "multipart/mixed;boundary=\"--blob-boundary"
 PASS expectedMimeType is "multipart/mixed;boundary=\"--blob-boundary\""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
index b0aeed9..213936d 100644 (file)
@@ -12,7 +12,7 @@ PASS expectedMimeType is ""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
-PASS expectedMimeType is ""
+PASS expectedMimeType is "multipart/mixed;boundary=\"--blob-boundary"
 PASS expectedMimeType is "multipart/mixed;boundary=\"--blob-boundary\""
 PASS expectedMimeType is ""
 PASS expectedMimeType is ""
index 49852f2..ce5f35b 100644 (file)
@@ -23,7 +23,7 @@ var xhrBlobTestCases = [{
     expectedMime: ''
 }, {
     mime: 'multipart/mixed;boundary="--blob-boundary',
-    expectedMime: ''
+    expectedMime: 'multipart/mixed;boundary="--blob-boundary'
 }, {
     mime: 'multipart/mixed;boundary="--blob-boundary"',
     expectedMime: 'multipart/mixed;boundary="--blob-boundary"'
index 4db3d20..7778e1f 100644 (file)
@@ -1,3 +1,14 @@
+2019-01-23  Rob Buis  <rbuis@igalia.com>
+
+        Update MIME type parser
+        https://bugs.webkit.org/show_bug.cgi?id=180526
+
+        Reviewed by Frédéric Wang.
+
+        Update improved test expectations.
+
+        * web-platform-tests/xhr/overridemimetype-blob-expected.txt:
+
 2019-01-22  Youenn Fablet  <youenn@apple.com>
 
         Resync libwebrtc with latest M72 branch
index 02561ca..7de610d 100644 (file)
@@ -4,59 +4,59 @@ PASS Use text/xml as fallback MIME type, 2
 PASS Loading data… 
 FAIL 1) MIME types need to be parsed and serialized: text/html;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
 FAIL 2) MIME types need to be parsed and serialized: TEXT/HTML;CHARSET=GBK assert_equals: expected "text/html;charset=GBK" but got "text/html"
-FAIL 3) MIME types need to be parsed and serialized: text/html;charset=gbk( assert_equals: expected "text/html;charset=\"gbk(\"" but got "application/octet-stream"
-FAIL 4) MIME types need to be parsed and serialized: text/html;x=(;charset=gbk assert_equals: expected "text/html;x=\"(\";charset=gbk" but got "application/octet-stream"
+FAIL 3) MIME types need to be parsed and serialized: text/html;charset=gbk( assert_equals: expected "text/html;charset=\"gbk(\"" but got "text/html"
+FAIL 4) MIME types need to be parsed and serialized: text/html;x=(;charset=gbk assert_equals: expected "text/html;x=\"(\";charset=gbk" but got "text/html"
 FAIL 5) MIME types need to be parsed and serialized: text/html;charset=gbk;charset=windows-1255 assert_equals: expected "text/html;charset=gbk" but got "text/html"
-FAIL 6) MIME types need to be parsed and serialized: text/html;charset=();charset=GBK assert_equals: expected "text/html;charset=\"()\"" but got "application/octet-stream"
-FAIL 7) MIME types need to be parsed and serialized: text/html;charset =gbk assert_equals: expected "text/html" but got "application/octet-stream"
+FAIL 6) MIME types need to be parsed and serialized: text/html;charset=();charset=GBK assert_equals: expected "text/html;charset=\"()\"" but got "text/html"
+PASS 7) MIME types need to be parsed and serialized: text/html;charset =gbk 
 FAIL 8) MIME types need to be parsed and serialized: text/html ;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
 FAIL 9) MIME types need to be parsed and serialized: text/html; charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
-FAIL 10) MIME types need to be parsed and serialized: text/html;charset= gbk assert_equals: expected "text/html;charset=\" gbk\"" but got "application/octet-stream"
-FAIL 11) MIME types need to be parsed and serialized: text/html;charset= "gbk" assert_equals: expected "text/html;charset=\" \\\"gbk\\"\"" but got "application/octet-stream"
+FAIL 10) MIME types need to be parsed and serialized: text/html;charset= gbk assert_equals: expected "text/html;charset=\" gbk\"" but got "text/html"
+FAIL 11) MIME types need to be parsed and serialized: text/html;charset= "gbk" assert_equals: expected "text/html;charset=\" \\\"gbk\\"\"" but got "text/html"
 FAIL 12) MIME types need to be parsed and serialized: text/html;charset='gbk' assert_equals: expected "text/html;charset='gbk'" but got "text/html"
 FAIL 13) MIME types need to be parsed and serialized: text/html;charset='gbk assert_equals: expected "text/html;charset='gbk" but got "text/html"
 FAIL 14) MIME types need to be parsed and serialized: text/html;charset=gbk' assert_equals: expected "text/html;charset=gbk'" but got "text/html"
 FAIL 15) MIME types need to be parsed and serialized: text/html;charset=';charset=GBK assert_equals: expected "text/html;charset='" but got "text/html"
-FAIL 16) MIME types need to be parsed and serialized: text/html;test;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
+FAIL 16) MIME types need to be parsed and serialized: text/html;test;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
 FAIL 17) MIME types need to be parsed and serialized: text/html;test=;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 18) MIME types need to be parsed and serialized: text/html;';charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 19) MIME types need to be parsed and serialized: text/html;";charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 20) MIME types need to be parsed and serialized: text/html ; ; charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 21) MIME types need to be parsed and serialized: text/html;;;;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 22) MIME types need to be parsed and serialized: text/html;charset= "\7f;charset=GBK assert_equals: expected "text/html;charset=GBK" but got "application/octet-stream"
+FAIL 18) MIME types need to be parsed and serialized: text/html;';charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
+FAIL 19) MIME types need to be parsed and serialized: text/html;";charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
+FAIL 20) MIME types need to be parsed and serialized: text/html ; ; charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
+FAIL 21) MIME types need to be parsed and serialized: text/html;;;;charset=gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
+FAIL 22) MIME types need to be parsed and serialized: text/html;charset= "\7f;charset=GBK assert_equals: expected "text/html;charset=GBK" but got "text/html"
 FAIL 23) MIME types need to be parsed and serialized: text/html;charset="\7f;charset=foo";charset=GBK assert_equals: expected "text/html;charset=GBK" but got "text/html"
 FAIL 24) MIME types need to be parsed and serialized: text/html;charset="gbk" assert_equals: expected "text/html;charset=gbk" but got "text/html"
-FAIL 25) MIME types need to be parsed and serialized: text/html;charset="gbk assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
-FAIL 26) MIME types need to be parsed and serialized: text/html;charset=gbk" assert_equals: expected "text/html;charset=\"gbk\\\"\"" but got "application/octet-stream"
+FAIL 25) MIME types need to be parsed and serialized: text/html;charset="gbk assert_equals: expected "text/html;charset=gbk" but got "text/html"
+FAIL 26) MIME types need to be parsed and serialized: text/html;charset=gbk" assert_equals: expected "text/html;charset=\"gbk\\\"\"" but got "text/html"
 FAIL 27) MIME types need to be parsed and serialized: text/html;charset=" gbk" assert_equals: expected "text/html;charset=\" gbk\"" but got "text/html"
 FAIL 28) MIME types need to be parsed and serialized: text/html;charset="gbk " assert_equals: expected "text/html;charset=\"gbk \"" but got "text/html"
 FAIL 29) MIME types need to be parsed and serialized: text/html;charset="\ gbk" assert_equals: expected "text/html;charset=\" gbk\"" but got "text/html"
 FAIL 30) MIME types need to be parsed and serialized: text/html;charset="\g\b\k" assert_equals: expected "text/html;charset=gbk" but got "text/html"
-FAIL 31) MIME types need to be parsed and serialized: text/html;charset="gbk"x assert_equals: expected "text/html;charset=gbk" but got "application/octet-stream"
+FAIL 31) MIME types need to be parsed and serialized: text/html;charset="gbk"x assert_equals: expected "text/html;charset=gbk" but got "text/html"
 FAIL 32) MIME types need to be parsed and serialized: text/html;charset="";charset=GBK assert_equals: expected "text/html;charset=\"\"" but got "text/html"
-FAIL 33) MIME types need to be parsed and serialized: text/html;charset=";charset=GBK assert_equals: expected "text/html;charset=\";charset=GBK\"" but got "application/octet-stream"
+FAIL 33) MIME types need to be parsed and serialized: text/html;charset=";charset=GBK assert_equals: expected "text/html;charset=\";charset=GBK\"" but got "text/html"
 FAIL 34) MIME types need to be parsed and serialized: text/html;charset={gbk} assert_equals: expected "text/html;charset=\"{gbk}\"" but got "text/html"
 FAIL 35) MIME types need to be parsed and serialized: text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk assert_equals: expected "text/html;0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789=x;charset=gbk" but got "text/html"
 PASS 36) MIME types need to be parsed and serialized: 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789/0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 
 FAIL 37) MIME types need to be parsed and serialized: !#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz assert_equals: expected "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz;!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz=!#$%&'*+-.^_`|~0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" but got "!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz/!#$%&'*+-.^_`|~0123456789abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz"
 FAIL 38) MIME types need to be parsed and serialized: x/x;x="   !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\80\81\82\83\84\85\86\87\88\89\8a\8b\8c\8d\8e\8f\90\91\92\93\94\95\96\97\98\99\9a\9b\9c\9d\9e\9f ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ" assert_equals: expected "x/x;x=\"\t !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\80\81\82\83\84\85\86\87\88\89\8a\8b\8c\8d\8e\8f\90\91\92\93\94\95\96\97\98\99\9a\9b\9c\9d\9e\9f ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ\"" but got "x/x"
-FAIL 39) MIME types need to be parsed and serialized: x/x;test assert_equals: expected "x/x" but got "application/octet-stream"
-FAIL 40) MIME types need to be parsed and serialized: x/x;test="\ assert_equals: expected "x/x;test=\"\\\\"" but got "application/octet-stream"
-FAIL 41) MIME types need to be parsed and serialized: x/x;x=  assert_equals: expected "x/x" but got "application/octet-stream"
-FAIL 42) MIME types need to be parsed and serialized: x/x;x=    assert_equals: expected "x/x" but got "application/octet-stream"
-FAIL 43) MIME types need to be parsed and serialized: text/html;test=ÿ;charset=gbk assert_equals: expected "text/html;test=\"ÿ\";charset=gbk" but got "application/octet-stream"
-FAIL 44) MIME types need to be parsed and serialized: x/x;test=�;x=x assert_equals: expected "x/x;x=x" but got "application/octet-stream"
+PASS 39) MIME types need to be parsed and serialized: x/x;test 
+FAIL 40) MIME types need to be parsed and serialized: x/x;test="\ assert_equals: expected "x/x;test=\"\\\\"" but got "x/x"
+PASS 41) MIME types need to be parsed and serialized: x/x;x=  
+PASS 42) MIME types need to be parsed and serialized: x/x;x=    
+FAIL 43) MIME types need to be parsed and serialized: text/html;test=ÿ;charset=gbk assert_equals: expected "text/html;test=\"ÿ\";charset=gbk" but got "text/html"
+FAIL 44) MIME types need to be parsed and serialized: x/x;test=�;x=x assert_equals: expected "x/x;x=x" but got "x/x"
 PASS 45) MIME types need to be parsed and serialized:  
 PASS 46) MIME types need to be parsed and serialized:   
 PASS 47) MIME types need to be parsed and serialized: / 
 PASS 48) MIME types need to be parsed and serialized: bogus 
 PASS 49) MIME types need to be parsed and serialized: bogus/ 
-PASS 50) MIME types need to be parsed and serialized: bogus/  
+FAIL 50) MIME types need to be parsed and serialized: bogus/  assert_equals: expected "application/octet-stream" but got "bogus/"
 PASS 51) MIME types need to be parsed and serialized: bogus/bogus/; 
 PASS 52) MIME types need to be parsed and serialized: </> 
 PASS 53) MIME types need to be parsed and serialized: (/) 
 PASS 54) MIME types need to be parsed and serialized: ÿ/ÿ 
-FAIL 55) MIME types need to be parsed and serialized: text/html(;doesnot=matter assert_equals: expected "application/octet-stream" but got "text/html("
+PASS 55) MIME types need to be parsed and serialized: text/html(;doesnot=matter 
 FAIL 56) MIME types need to be parsed and serialized: {/} assert_equals: expected "application/octet-stream" but got "{/}"
 PASS 57) MIME types need to be parsed and serialized: Ā/Ā 
 
index 57e0d64..0c2ff4f 100644 (file)
@@ -1,3 +1,50 @@
+2019-01-23  Rob Buis  <rbuis@igalia.com>
+
+        Update MIME type parser
+        https://bugs.webkit.org/show_bug.cgi?id=180526
+
+        Reviewed by Frédéric Wang.
+
+        Add an enum to allow two modes of MIME type parsing, one mode
+        to keep supporting RFC2045 as before, and one mode to support
+        the updated MIME parser from mimesniff [1]. Mimesniff support
+        brings the following changes:
+        - allows parameter names without matching =value.
+        - skips whitespace after subtype, parameter value and before
+          parameter name.
+        - lower cases MIME type and parameter name.
+        - parameter names parsed before are discarded.
+
+        The old mode is still used by CDM.cpp and MIMEHeader.cpp.
+
+        [1] https://mimesniff.spec.whatwg.org/
+
+        * Modules/encryptedmedia/CDM.cpp:
+        (WebCore::CDM::getSupportedCapabilitiesForAudioVideoType):
+        * platform/network/MIMEHeader.cpp:
+        (WebCore::MIMEHeader::parseHeader):
+        * platform/network/ParsedContentType.cpp:
+        (WebCore::DummyParsedContentType::setContentType const):
+        (WebCore::DummyParsedContentType::setContentTypeParameter const):
+        (WebCore::isQuotedStringTokenCharacter):
+        (WebCore::isTokenCharacter):
+        (WebCore::parseToken):
+        (WebCore::containsNonTokenCharacters):
+        (WebCore::parseQuotedString):
+        (WebCore::isNotForwardSlash):
+        (WebCore::isNotSemicolon):
+        (WebCore::isNotSemicolonOrEqualSign):
+        (WebCore::parseContentType):
+        (WebCore::isValidContentType):
+        (WebCore::ParsedContentType::ParsedContentType):
+        (WebCore::ParsedContentType::setContentType):
+        (WebCore::isNonTokenCharacter):
+        (WebCore::isNonQuotedStringTokenCharacter):
+        (WebCore::ParsedContentType::setContentTypeParameter):
+        * platform/network/ParsedContentType.h:
+
+        Test: web-platform-tests/xhr/overridemimetype-blob.html
+
 2019-01-22  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         Introduce CustomUndoStep.h and CustomUndoStep.cpp
index 9356650..4fe250b 100644 (file)
@@ -433,7 +433,7 @@ Optional<Vector<MediaKeySystemMediaCapability>> CDM::getSupportedCapabilitiesFor
             return WTF::nullopt;
 
         // 3.4. If content type is an invalid or unrecognized MIME type, continue to the next iteration.
-        if (!isValidContentType(requestedCapability.contentType))
+        if (!isValidContentType(requestedCapability.contentType, Mode::Rfc2045))
             continue;
 
         // 3.5. Let container be the container type specified by content type.
index 251693e..b1daceb 100644 (file)
@@ -87,7 +87,7 @@ RefPtr<MIMEHeader> MIMEHeader::parseHeader(SharedBufferChunkReader& buffer)
     KeyValueMap keyValuePairs = retrieveKeyValuePairs(buffer);
     KeyValueMap::iterator mimeParametersIterator = keyValuePairs.find("content-type");
     if (mimeParametersIterator != keyValuePairs.end()) {
-        ParsedContentType parsedContentType(mimeParametersIterator->value);
+        ParsedContentType parsedContentType(mimeParametersIterator->value, Mode::Rfc2045);
         mimeHeader->m_contentType = parsedContentType.mimeType();
         if (!mimeHeader->isMultipart())
             mimeHeader->m_charset = parsedContentType.charset().stripWhiteSpace();
index 9e8f7e1..e3eabd9 100644 (file)
@@ -38,8 +38,8 @@ namespace WebCore {
 
 class DummyParsedContentType {
 public:
-    void setContentType(const SubstringRange&) const { }
-    void setContentTypeParameter(const SubstringRange&, const SubstringRange&) const { }
+    void setContentType(const SubstringRange&, Mode) const { }
+    void setContentTypeParameter(const String&, const String&, Mode) const { }
 };
 
 static void skipSpaces(const String& input, unsigned& startIndex)
@@ -48,12 +48,19 @@ static void skipSpaces(const String& input, unsigned& startIndex)
         ++startIndex;
 }
 
-static bool isTokenCharacter(char c)
+static bool isQuotedStringTokenCharacter(UChar c)
+{
+    return (c >= ' ' && c <= '~') || (c >= 0x80 && c <= 0xFF) || c == '\t';
+}
+
+static bool isTokenCharacter(UChar c)
 {
     return isASCII(c) && c > ' ' && c != '"' && c != '(' && c != ')' && c != ',' && c != '/' && (c < ':' || c > '@') && (c < '[' || c > ']');
 }
 
-static Optional<SubstringRange> parseToken(const String& input, unsigned& startIndex)
+using CharacterMeetsCondition = bool (*)(UChar);
+
+static Optional<SubstringRange> parseToken(const String& input, unsigned& startIndex, CharacterMeetsCondition characterMeetsCondition, Mode mode, bool skipTrailingWhitespace = false)
 {
     unsigned inputLength = input.length();
     unsigned tokenStart = startIndex;
@@ -62,18 +69,31 @@ static Optional<SubstringRange> parseToken(const String& input, unsigned& startI
     if (tokenEnd >= inputLength)
         return WTF::nullopt;
 
-    while (tokenEnd < inputLength) {
-        if (!isTokenCharacter(input[tokenEnd]))
+    while (tokenEnd < inputLength && characterMeetsCondition(input[tokenEnd])) {
+        if (mode == Mode::Rfc2045 && !isTokenCharacter(input[tokenEnd]))
             break;
         ++tokenEnd;
     }
 
     if (tokenEnd == tokenStart)
         return WTF::nullopt;
+    if (skipTrailingWhitespace) {
+        while (input[tokenEnd - 1] == ' ')
+            --tokenEnd;
+    }
     return SubstringRange(tokenStart, tokenEnd - tokenStart);
 }
 
-static Optional<SubstringRange> parseQuotedString(const String& input, unsigned& startIndex)
+static bool containsNonTokenCharacters(const String& input, SubstringRange range)
+{
+    for (unsigned index = 0; index < range.second; ++index) {
+        if (!isTokenCharacter(input[range.first + index]))
+            return true;
+    }
+    return false;
+}
+
+static Optional<SubstringRange> parseQuotedString(const String& input, unsigned& startIndex, Mode mode)
 {
     unsigned inputLength = input.length();
     unsigned quotedStringStart = startIndex + 1;
@@ -88,8 +108,11 @@ static Optional<SubstringRange> parseQuotedString(const String& input, unsigned&
     bool lastCharacterWasBackslash = false;
     char currentCharacter;
     while ((currentCharacter = input[quotedStringEnd++]) != '"' || lastCharacterWasBackslash) {
-        if (quotedStringEnd >= inputLength)
-            return WTF::nullopt;
+        if (quotedStringEnd >= inputLength) {
+            if (mode == Mode::Rfc2045)
+                return WTF::nullopt;
+            break;
+        }
         if (currentCharacter == '\\' && !lastCharacterWasBackslash) {
             lastCharacterWasBackslash = true;
             continue;
@@ -151,8 +174,23 @@ static String substringForRange(const String& string, const SubstringRange& rang
 //               ; Must be in quoted-string,
 //               ; to use within parameter values
 
+static bool isNotForwardSlash(UChar ch)
+{
+    return ch != '/';
+}
+
+static bool isNotSemicolon(UChar ch)
+{
+    return ch != ';';
+}
+
+static bool isNotSemicolonOrEqualSign(UChar ch)
+{
+    return ch != ';' && ch != '=';
+}
+
 template <class ReceiverType>
-bool parseContentType(const String& contentType, ReceiverType& receiver)
+bool parseContentType(const String& contentType, ReceiverType& receiver, Mode mode)
 {
     unsigned index = 0;
     unsigned contentTypeLength = contentType.length();
@@ -163,8 +201,8 @@ bool parseContentType(const String& contentType, ReceiverType& receiver)
     }
 
     unsigned contentTypeStart = index;
-    auto typeRange = parseToken(contentType, index);
-    if (!typeRange) {
+    auto typeRange = parseToken(contentType, index, isNotForwardSlash, mode);
+    if (!typeRange || containsNonTokenCharacters(contentType, *typeRange)) {
         LOG_ERROR("Invalid Content-Type, invalid type value.");
         return false;
     }
@@ -174,8 +212,8 @@ bool parseContentType(const String& contentType, ReceiverType& receiver)
         return false;
     }
 
-    auto subTypeRange = parseToken(contentType, index);
-    if (!subTypeRange) {
+    auto subTypeRange = parseToken(contentType, index, isNotSemicolon, mode, mode == Mode::MimeSniff);
+    if (!subTypeRange || containsNonTokenCharacters(contentType, *subTypeRange)) {
         LOG_ERROR("Invalid Content-Type, invalid subtype value.");
         return false;
     }
@@ -183,46 +221,61 @@ bool parseContentType(const String& contentType, ReceiverType& receiver)
     // There should not be any quoted strings until we reach the parameters.
     size_t semiColonIndex = contentType.find(';', contentTypeStart);
     if (semiColonIndex == notFound) {
-        receiver.setContentType(SubstringRange(contentTypeStart, contentTypeLength - contentTypeStart));
+        receiver.setContentType(SubstringRange(contentTypeStart, contentTypeLength - contentTypeStart), mode);
         return true;
     }
 
-    receiver.setContentType(SubstringRange(contentTypeStart, semiColonIndex - contentTypeStart));
+    receiver.setContentType(SubstringRange(contentTypeStart, semiColonIndex - contentTypeStart), mode);
     index = semiColonIndex + 1;
     while (true) {
         skipSpaces(contentType, index);
-        auto keyRange = parseToken(contentType, index);
-        if (!keyRange || index >= contentTypeLength) {
+        auto keyRange = parseToken(contentType, index, isNotSemicolonOrEqualSign, mode);
+        if (mode == Mode::Rfc2045 && (!keyRange || index >= contentTypeLength)) {
             LOG_ERROR("Invalid Content-Type parameter name.");
             return false;
         }
 
         // Should we tolerate spaces here?
-        if (contentType[index++] != '=' || index >= contentTypeLength) {
-            LOG_ERROR("Invalid Content-Type malformed parameter.");
-            return false;
+        if (mode == Mode::Rfc2045) {
+            if (contentType[index++] != '=' || index >= contentTypeLength) {
+                LOG_ERROR("Invalid Content-Type malformed parameter.");
+                return false;
+            }
+        } else {
+            if (index >= contentTypeLength)
+                break;
+            if (contentType[index] != '=' && contentType[index] != ';') {
+                LOG_ERROR("Invalid Content-Type malformed parameter.");
+                return false;
+            }
+            if (contentType[index++] == ';')
+                continue;
         }
 
+        String parameterName = substringForRange(contentType, *keyRange);
+
         // Should we tolerate spaces here?
-        String value;
         Optional<SubstringRange> valueRange;
-        if (contentType[index] == '"')
-            valueRange = parseQuotedString(contentType, index);
-        else
-            valueRange = parseToken(contentType, index);
+        if (contentType[index] == '"') {
+            valueRange = parseQuotedString(contentType, index, mode);
+            if (mode == Mode::MimeSniff)
+                parseToken(contentType, index, isNotSemicolon, mode);
+        } else
+            valueRange = parseToken(contentType, index, isNotSemicolon, mode, mode == Mode::MimeSniff);
 
         if (!valueRange) {
             LOG_ERROR("Invalid Content-Type, invalid parameter value.");
             return false;
         }
 
+        String parameterValue = substringForRange(contentType, *valueRange);
         // Should we tolerate spaces here?
-        if (index < contentTypeLength && contentType[index++] != ';') {
+        if (mode == Mode::Rfc2045 && index < contentTypeLength && contentType[index++] != ';') {
             LOG_ERROR("Invalid Content-Type, invalid character at the end of key/value parameter.");
             return false;
         }
 
-        receiver.setContentTypeParameter(*keyRange, *valueRange);
+        receiver.setContentTypeParameter(parameterName, parameterValue, mode);
 
         if (index >= contentTypeLength)
             return true;
@@ -231,19 +284,19 @@ bool parseContentType(const String& contentType, ReceiverType& receiver)
     return true;
 }
 
-bool isValidContentType(const String& contentType)
+bool isValidContentType(const String& contentType, Mode mode)
 {
     if (contentType.contains('\r') || contentType.contains('\n'))
         return false;
 
     DummyParsedContentType parsedContentType = DummyParsedContentType();
-    return parseContentType<DummyParsedContentType>(contentType, parsedContentType);
+    return parseContentType<DummyParsedContentType>(contentType, parsedContentType, mode);
 }
 
-ParsedContentType::ParsedContentType(const String& contentType)
+ParsedContentType::ParsedContentType(const String& contentType, Mode mode)
     : m_contentType(contentType.stripWhiteSpace())
 {
-    parseContentType<ParsedContentType>(m_contentType, *this);
+    parseContentType<ParsedContentType>(m_contentType, *this, mode);
 }
 
 String ParsedContentType::charset() const
@@ -261,14 +314,31 @@ size_t ParsedContentType::parameterCount() const
     return m_parameters.size();
 }
 
-void ParsedContentType::setContentType(const SubstringRange& contentRange)
+void ParsedContentType::setContentType(const SubstringRange& contentRange, Mode mode)
 {
     m_mimeType = substringForRange(m_contentType, contentRange).stripWhiteSpace();
+    if (mode == Mode::MimeSniff)
+        m_mimeType.convertToASCIILowercase();
 }
 
-void ParsedContentType::setContentTypeParameter(const SubstringRange& key, const SubstringRange& value)
+static bool isNonTokenCharacter(UChar ch)
 {
-    m_parameters.set(substringForRange(m_contentType, key), substringForRange(m_contentType, value));
+    return !isTokenCharacter(ch);
+}
+
+static bool isNonQuotedStringTokenCharacter(UChar ch)
+{
+    return !isQuotedStringTokenCharacter(ch);
+}
+
+void ParsedContentType::setContentTypeParameter(const String& keyName, const String& keyValue, Mode mode)
+{
+    if (mode == Mode::MimeSniff) {
+        if (m_parameters.contains(keyName) || keyName.find(isNonTokenCharacter) != notFound || keyValue.find(isNonQuotedStringTokenCharacter) != notFound)
+            return;
+        keyName.convertToASCIILowercase();
+    }
+    m_parameters.set(keyName, keyValue);
 }
 
 }
index 6363b3e..949010b 100644 (file)
 
 namespace WebCore {
 
+enum class Mode {
+    Rfc2045,
+    MimeSniff
+};
 // <index, length>
 typedef std::pair<unsigned, unsigned> SubstringRange;
-bool isValidContentType(const String&);
+WEBCORE_EXPORT bool isValidContentType(const String&, Mode = Mode::MimeSniff);
 
 // FIXME: add support for comments.
 class ParsedContentType {
 public:
-    explicit ParsedContentType(const String&);
+    explicit ParsedContentType(const String&, Mode = Mode::MimeSniff);
 
     String mimeType() const { return m_mimeType; }
     String charset() const;
@@ -54,9 +58,9 @@ public:
 
 private:
     template<class ReceiverType>
-    friend bool parseContentType(const String&, ReceiverType&);
-    void setContentType(const SubstringRange&);
-    void setContentTypeParameter(const SubstringRange&, const SubstringRange&);
+    friend bool parseContentType(const String&, ReceiverType&, Mode);
+    void setContentType(const SubstringRange&, Mode);
+    void setContentTypeParameter(const String&, const String&, Mode);
 
     typedef HashMap<String, String> KeyValuePairs;
     String m_contentType;
index 3165bd5..36dc525 100644 (file)
@@ -1,3 +1,16 @@
+2019-01-23  Rob Buis  <rbuis@igalia.com>
+
+        Update MIME type parser
+        https://bugs.webkit.org/show_bug.cgi?id=180526
+
+        Reviewed by Frédéric Wang.
+
+        Add unit tests for both parse modes of ParsedContentType.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebCore/ParsedContentType.cpp: Added.
+        (TestWebKitAPI::TEST):
+
 2019-01-22  Aakash Jain  <aakash_jain@apple.com>
 
         [ews-app] fetch loop should not stop on network issues
index 7a1072a..547d1eb 100644 (file)
                A5A729F11F622AA700DE5A28 /* WKNavigationResponse.mm in Sources */ = {isa = PBXBuildFile; fileRef = A5A729F01F622A9A00DE5A28 /* WKNavigationResponse.mm */; };
                A5B149DE1F5A19EA00C6DAFF /* MIMETypeRegistry.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5B149DD1F5A19DC00C6DAFF /* MIMETypeRegistry.cpp */; };
                A5E2027515B21F6E00C13E14 /* WindowlessWebViewWithMedia.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = A5E2027015B2180600C13E14 /* WindowlessWebViewWithMedia.html */; };
+               AA96CAB621C7DB5000FD2F97 /* ParsedContentType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA96CAB421C7DB4200FD2F97 /* ParsedContentType.cpp */; };
                AD57AC201DA7465000FF1BDE /* DidRemoveFrameFromHiearchyInPageCache_Bundle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD57AC1E1DA7464D00FF1BDE /* DidRemoveFrameFromHiearchyInPageCache_Bundle.cpp */; };
                AD57AC211DA7465B00FF1BDE /* DidRemoveFrameFromHiearchyInPageCache.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD57AC1F1DA7464D00FF1BDE /* DidRemoveFrameFromHiearchyInPageCache.cpp */; };
                AD57AC221DA7466E00FF1BDE /* many-iframes.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = AD57AC1D1DA7463800FF1BDE /* many-iframes.html */; };
                A5E2027015B2180600C13E14 /* WindowlessWebViewWithMedia.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = WindowlessWebViewWithMedia.html; sourceTree = "<group>"; };
                A5E2027215B2181900C13E14 /* WindowlessWebViewWithMedia.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WindowlessWebViewWithMedia.mm; sourceTree = "<group>"; };
                A7A966DA140ECCC8005EF9B4 /* CheckedArithmeticOperations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CheckedArithmeticOperations.cpp; sourceTree = "<group>"; };
+               AA96CAB421C7DB4200FD2F97 /* ParsedContentType.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParsedContentType.cpp; sourceTree = "<group>"; };
                ABF510632A19B8AC7EC40E17 /* AbortableTaskQueue.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = AbortableTaskQueue.cpp; sourceTree = "<group>"; };
                AD57AC1D1DA7463800FF1BDE /* many-iframes.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "many-iframes.html"; sourceTree = "<group>"; };
                AD57AC1E1DA7464D00FF1BDE /* DidRemoveFrameFromHiearchyInPageCache_Bundle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DidRemoveFrameFromHiearchyInPageCache_Bundle.cpp; sourceTree = "<group>"; };
                                CE1866471F72E8F100A0CAB6 /* MarkedText.cpp */,
                                A5B149DD1F5A19DC00C6DAFF /* MIMETypeRegistry.cpp */,
                                CD225C071C45A69200140761 /* ParsedContentRange.cpp */,
+                               AA96CAB421C7DB4200FD2F97 /* ParsedContentType.cpp */,
                                041A1E33216FFDBC00789E0A /* PublicSuffix.cpp */,
                                F418BE141F71B7DC001970E6 /* RoundedRectTests.cpp */,
                                CDCFA7A91E45122F00C2433D /* SampleMap.cpp */,
                                7CCE7EC71A411A7E00447C4C /* PageVisibilityStateWithWindowChanges.mm in Sources */,
                                7CCE7F091A411AE600447C4C /* ParentFrame.cpp in Sources */,
                                7C83E0511D0A641800FEBCF3 /* ParsedContentRange.cpp in Sources */,
+                               AA96CAB621C7DB5000FD2F97 /* ParsedContentType.cpp in Sources */,
                                F44C79FF20F9E8710014478C /* ParserYieldTokenTests.mm in Sources */,
                                7CCE7F0A1A411AE600447C4C /* PasteboardNotifications.mm in Sources */,
                                9BCB7C2820130600003E7C0C /* PasteHTML.mm in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebCore/ParsedContentType.cpp b/Tools/TestWebKitAPI/Tests/WebCore/ParsedContentType.cpp
new file mode 100644 (file)
index 0000000..45b8894
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2019 Igalia, S.L. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#include "Test.h"
+#include <WebCore/ParsedContentType.h>
+
+using namespace WebCore;
+
+namespace TestWebKitAPI {
+
+TEST(ParsedContentType, MimeSniff)
+{
+    EXPECT_TRUE(isValidContentType("text/plain", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType(" text/plain", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType(" text/plain ", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text /plain", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text/ plain", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text / plain", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("te xt/plain", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text/pla in", Mode::MimeSniff));
+
+    EXPECT_FALSE(isValidContentType("text", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text/", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("/plain", Mode::MimeSniff));
+
+    EXPECT_TRUE(isValidContentType("text/plain;", Mode::MimeSniff));
+
+    EXPECT_TRUE(isValidContentType("text/plain;test", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain; test", Mode::MimeSniff));
+    EXPECT_FALSE(isValidContentType("text/plain;test=", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test=value", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain; test=value", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test =value", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test= value", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test=value ", Mode::MimeSniff));
+
+    EXPECT_TRUE(isValidContentType("text/plain;test=\"value\"", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test=\"value", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test=\"value\"foo", Mode::MimeSniff));
+    EXPECT_TRUE(isValidContentType("text/plain;test=\"value\"foo;foo=bar", Mode::MimeSniff));
+}
+
+TEST(ParsedContentType, Rfc2045)
+{
+    EXPECT_TRUE(isValidContentType("text/plain", Mode::Rfc2045));
+    EXPECT_TRUE(isValidContentType(" text/plain", Mode::Rfc2045));
+    EXPECT_TRUE(isValidContentType(" text/plain ", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text /plain", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/ plain", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text / plain", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("te xt/plain", Mode::Rfc2045));
+    EXPECT_TRUE(isValidContentType("text/pla in", Mode::Rfc2045));
+
+    EXPECT_FALSE(isValidContentType("text", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("/plain", Mode::Rfc2045));
+
+    EXPECT_FALSE(isValidContentType("text/plain;", Mode::Rfc2045));
+
+    EXPECT_FALSE(isValidContentType("text/plain;test", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain; test", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test=", Mode::Rfc2045));
+    EXPECT_TRUE(isValidContentType("text/plain;test=value", Mode::Rfc2045));
+    EXPECT_TRUE(isValidContentType("text/plain; test=value", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test =value", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test= value", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test=value ", Mode::Rfc2045));
+
+    EXPECT_TRUE(isValidContentType("text/plain;test=\"value\"", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test=\"value", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test=\"value\"foo", Mode::Rfc2045));
+    EXPECT_FALSE(isValidContentType("text/plain;test=\"value\"foo;foo=bar", Mode::Rfc2045));
+}
+
+} // namespace TestWebKitAPI