diff --git a/dom/base/nsJSEnvironment.cpp b/dom/base/nsJSEnvironment.cpp index ff709ca78a..248267345b 100644 --- a/dom/base/nsJSEnvironment.cpp +++ b/dom/base/nsJSEnvironment.cpp @@ -2495,7 +2495,13 @@ NS_DOMReadStructuredClone(JSContext* cx, { if (tag == SCTAG_DOM_IMAGEDATA) { return ReadStructuredCloneImageData(cx, reader); - } else if (tag == SCTAG_DOM_WEBCRYPTO_KEY) { + } + + if (tag == SCTAG_DOM_WEBCRYPTO_KEY) { + if (!NS_IsMainThread()) { + return nullptr; + } + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); if (!global) { return nullptr; @@ -2512,9 +2518,15 @@ NS_DOMReadStructuredClone(JSContext* cx, } } return result; - } else if (tag == SCTAG_DOM_NULL_PRINCIPAL || - tag == SCTAG_DOM_SYSTEM_PRINCIPAL || - tag == SCTAG_DOM_CONTENT_PRINCIPAL) { + } + + if (tag == SCTAG_DOM_NULL_PRINCIPAL || + tag == SCTAG_DOM_SYSTEM_PRINCIPAL || + tag == SCTAG_DOM_CONTENT_PRINCIPAL) { + if (!NS_IsMainThread()) { + return nullptr; + } + mozilla::ipc::PrincipalInfo info; if (tag == SCTAG_DOM_SYSTEM_PRINCIPAL) { info = mozilla::ipc::SystemPrincipalInfo(); @@ -2552,8 +2564,14 @@ NS_DOMReadStructuredClone(JSContext* cx, } return result.toObjectOrNull(); - } else if (tag == SCTAG_DOM_NFC_NDEF) { + } + #ifdef MOZ_NFC + if (tag == SCTAG_DOM_NFC_NDEF) { + if (!NS_IsMainThread()) { + return nullptr; + } + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); if (!global) { return nullptr; @@ -2567,13 +2585,15 @@ NS_DOMReadStructuredClone(JSContext* cx, ndefRecord->WrapObject(cx, nullptr) : nullptr; } return result; -#else - return nullptr; -#endif } +#endif - if (tag == SCTAG_DOM_RTC_CERTIFICATE) { #ifdef MOZ_WEBRTC + if (tag == SCTAG_DOM_RTC_CERTIFICATE) { + if (!NS_IsMainThread()) { + return nullptr; + } + nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(cx)); if (!global) { return nullptr; @@ -2590,10 +2610,8 @@ NS_DOMReadStructuredClone(JSContext* cx, } } return result; -#else - return nullptr; -#endif } +#endif // Don't know what this is. Bail. xpc::Throw(cx, NS_ERROR_DOM_DATA_CLONE_ERR); @@ -2615,6 +2633,7 @@ NS_DOMWriteStructuredClone(JSContext* cx, // Handle Key cloning CryptoKey* key; if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, obj, key))) { + MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread."); return JS_WriteUint32Pair(writer, SCTAG_DOM_WEBCRYPTO_KEY, 0) && key->WriteStructuredClone(writer); } @@ -2623,12 +2642,13 @@ NS_DOMWriteStructuredClone(JSContext* cx, // Handle WebRTC Certificate cloning RTCCertificate* cert; if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, obj, cert))) { + MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread."); return JS_WriteUint32Pair(writer, SCTAG_DOM_RTC_CERTIFICATE, 0) && cert->WriteStructuredClone(writer); } #endif - if (xpc::IsReflector(obj)) { + if (NS_IsMainThread() && xpc::IsReflector(obj)) { nsCOMPtr base = xpc::UnwrapReflectorToISupports(obj); nsCOMPtr principal = do_QueryInterface(base); if (principal) { @@ -2656,6 +2676,7 @@ NS_DOMWriteStructuredClone(JSContext* cx, #ifdef MOZ_NFC MozNDEFRecord* ndefRecord; if (NS_SUCCEEDED(UNWRAP_OBJECT(MozNDEFRecord, obj, ndefRecord))) { + MOZ_ASSERT(NS_IsMainThread(), "This object should not be exposed outside the main-thread."); return JS_WriteUint32Pair(writer, SCTAG_DOM_NFC_NDEF, 0) && ndefRecord->WriteStructuredClone(cx, writer); } diff --git a/dom/base/test/file_pluginAudio.html b/dom/base/test/file_pluginAudio.html new file mode 100644 index 0000000000..02ef8bd02b --- /dev/null +++ b/dom/base/test/file_pluginAudio.html @@ -0,0 +1,21 @@ + + + diff --git a/dom/base/test/iframe_cloning_fileList.html b/dom/base/test/iframe_postMessages.html similarity index 67% rename from dom/base/test/iframe_cloning_fileList.html rename to dom/base/test/iframe_postMessages.html index 68c67085d8..b34462f5e8 100644 --- a/dom/base/test/iframe_cloning_fileList.html +++ b/dom/base/test/iframe_postMessages.html @@ -3,7 +3,7 @@ diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index fab8ce86a3..7968c50f7c 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -239,8 +239,11 @@ support-files = img_referrer_testserver.sjs file_webaudioLoop.html file_webaudioLoop2.html + file_pluginAudio.html referrer_helper.js referrer_testserver.sjs + script_postmessages_fileList.js + iframe_postMessages.html [test_anonymousContent_api.html] [test_anonymousContent_append_after_reflow.html] @@ -296,6 +299,8 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e1 [test_open_null_features.html] skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, tracked in bug 1011874 [test_postMessage_solidus.html] +[test_pluginAudioNotification.html] +skip-if = (buildapp == 'b2g' || buildapp == 'mulet' || toolkit == 'android') # Plugins don't work on Android and/or B2G/Mulet [test_screen_orientation.html] [test_settimeout_extra_arguments.html] [test_settimeout_inner.html] @@ -664,6 +669,11 @@ support-files = referrerHelper.js [test_img_referrer.html] [test_anchor_area_referrer.html] [test_anchor_area_referrer_changing.html] +[test_anchor_area_referrer_invalid.html] +[test_anchor_area_referrer_rel.html] +[test_iframe_referrer.html] +[test_iframe_referrer_changing.html] +[test_iframe_referrer_invalid.html] [test_caretPositionFromPoint.html] [test_classList.html] # This test fails on the Mac for some reason @@ -786,5 +796,5 @@ skip-if = buildapp == 'mulet' || buildapp == 'b2g' [test_file_negative_date.html] [test_nonascii_blob_url.html] [test_window_element_enumeration.html] -[test_cloning_fileList.html] -support-files = script_cloning_fileList.js iframe_cloning_fileList.html +[test_referrer_redirect.html] +[test_postMessages.html] diff --git a/dom/base/test/referrer_helper.js b/dom/base/test/referrer_helper.js index b21899b0e0..85bd124489 100644 --- a/dom/base/test/referrer_helper.js +++ b/dom/base/test/referrer_helper.js @@ -1,8 +1,8 @@ /* * common functionality for iframe, anchor, and area referrer attribute tests */ -const GET_RESULT = sjs + 'action=get-test-results'; -const RESET_STATE = sjs + 'action=resetState'; +const GET_RESULT = SJS + 'ACTION=get-test-results'; +const RESET_STATE = SJS + 'ACTION=resetState'; SimpleTest.waitForExplicitFinish(); var advance = function() { tests.next(); }; @@ -80,14 +80,14 @@ var tests = (function() { for (var i = 0; i < tests.length; i++) { yield resetState(); var searchParams = new URLSearchParams(); - searchParams.append(ACTION, actionString); - searchParams.append(NAME, tests[i].NAME); + searchParams.append("ACTION", actionString); + searchParams.append("NAME", tests[i].NAME); for (var l of PARAMS) { if (tests[i][l]) { - searchParams.append(window[l], tests[i][l]); + searchParams.append(l, tests[i][l]); } } - yield iframe.src = sjs + searchParams.toString(); + yield iframe.src = SJS + searchParams.toString(); yield checkIndividualResults(tests[i].DESC, tests[i].RESULT, tests[i].NAME); }; }; diff --git a/dom/base/test/referrer_testserver.sjs b/dom/base/test/referrer_testserver.sjs index c0f8223436..0299baaa16 100644 --- a/dom/base/test/referrer_testserver.sjs +++ b/dom/base/test/referrer_testserver.sjs @@ -1,23 +1,70 @@ /* * Test server for iframe, anchor, and area referrer attributes. * https://bugzilla.mozilla.org/show_bug.cgi?id=1175736 +* Also server for further referrer tests such as redirecting tests +* bug 1174913, bug 1175736, bug 1184781 */ Components.utils.importGlobalProperties(["URLSearchParams"]); -const BASE_URL = 'example.com/tests/dom/base/test/referrer_testserver.sjs'; -const SHARED_KEY = 'referrer_testserver.sjs'; -const ATTRIBUTE_POLICY = 'attributePolicy'; -const NEW_ATTRIBUTE_POLICY = 'newAttributePolicy'; -const NAME = 'name'; -const META_POLICY = 'metaPolicy'; -const REL = 'rel'; +const SJS = "referrer_testserver.sjs?"; +const BASE_URL = "example.com/tests/dom/base/test/" + SJS; +const SHARED_KEY = SJS; +const SAME_ORIGIN = "mochi.test:8888/tests/dom/base/test/" + SJS; +const CROSS_ORIGIN = "test1.example.com/tests/dom/base/test/" + SJS; + +const IMG_BYTES = atob( + "iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12" + + "P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="); function createTestUrl(aPolicy, aAction, aName, aType) { - return 'http://' + BASE_URL + '?' + - 'action=' + aAction + '&' + - 'policy=' + aPolicy + '&' + - 'name=' + aName + '&' + - 'type=' + aType; + return "http://" + BASE_URL + + "ACTION=" + aAction + "&" + + "policy=" + aPolicy + "&" + + "NAME=" + aName + "&" + + "type=" + aType; +} + +// test page using iframe referrer attribute +// if aParams are set this creates a test where the iframe url is a redirect +function createIframeTestPageUsingRefferer(aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aParams, aChangingMethod) { + var metaString = ""; + if (aMetaPolicy) { + metaString = ``; + } + var changeString = ""; + if (aChangingMethod === "setAttribute") { + changeString = `document.getElementById("myframe").setAttribute("referrer", "${aNewAttributePolicy}")`; + } else if (aChangingMethod === "property") { + changeString = `document.getElementById("myframe").referrer = "${aNewAttributePolicy}"`; + } + var iFrameString = ``; + var iframeUrl = ""; + if (aParams) { + aParams.delete("ACTION"); + aParams.append("ACTION", "redirectIframe"); + iframeUrl = "http://" + CROSS_ORIGIN + aParams.toString(); + } else { + iframeUrl = createTestUrl(aAttributePolicy, "test", aName, "iframe"); + } + + return ` + + + ${metaString} + + + ${iFrameString} + + + `; } function buildAnchorString(aMetaPolicy, aReferrerPolicy, aName, aRelString){ @@ -42,17 +89,17 @@ function buildAreaString(aMetaPolicy, aReferrerPolicy, aName, aRelString){ // test page using anchor or area referrer attribute function createAETestPageUsingRefferer(aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aRel, aStringBuilder, aChangingMethod) { - var metaString = ''; + var metaString = ""; if (aMetaPolicy) { - metaString = ''; + metaString = ``; } - var changeString = ''; - if (aChangingMethod === 'setAttribute') { + var changeString = ""; + if (aChangingMethod === "setAttribute") { changeString = `document.getElementById("link").setAttribute("referrer", "${aNewAttributePolicy}")`; - } else if (aChangingMethod === 'property') { + } else if (aChangingMethod === "property") { changeString = `document.getElementById("link").referrer = "${aNewAttributePolicy}"`; } - var relString = ''; + var relString = ""; if (aRel) { relString = `rel="noreferrer"`; } @@ -73,41 +120,84 @@ function createAETestPageUsingRefferer(aMetaPolicy, aAttributePolicy, aNewAttrib `; } +// creates test page with img that is a redirect +function createRedirectImgTestCase(aParams, aAttributePolicy) { + var metaString = ""; + if (aParams.has("META_POLICY")) { + metaString = ``; + } + aParams.delete("ACTION"); + aParams.append("ACTION", "redirectImg"); + var imgUrl = "http://" + CROSS_ORIGIN + aParams.toString(); + + return ` + + + + ${metaString} + Test referrer policies on redirect (img) + + + + + + `; +} + function handleRequest(request, response) { var params = new URLSearchParams(request.queryString); - var action = params.get('action'); + var action = params.get("ACTION"); - response.setHeader('Cache-Control', 'no-cache', false); - response.setHeader('Content-Type', 'text/html; charset=utf-8', false); - - if (action === 'resetState') { + if (action === "resetState") { setSharedState(SHARED_KEY, "{}"); response.write(""); return; } - if (action === 'get-test-results') { + if (action === "get-test-results") { // ?action=get-result - response.setHeader('Cache-Control', 'no-cache', false); - response.setHeader('Content-Type', 'text/plain', false); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/plain", false); response.write(getSharedState(SHARED_KEY)); return; } - if (action === 'redirect') { + if (action === "redirect") { response.write(''); return; } - if (action === 'test') { + if (action === "redirectImg"){ + params.delete("ACTION"); + params.append("ACTION", "test"); + params.append("type", "img"); + // 302 found, 301 Moved Permanently, 303 See Other, 307 Temporary Redirect + response.setStatusLine("1.1", 302, "found"); + response.setHeader("Location", "http://" + CROSS_ORIGIN + params.toString(), false); + return; + } + if (action === "redirectIframe"){ + params.delete("ACTION"); + params.append("ACTION", "test"); + params.append("type", "iframe"); + // 302 found, 301 Moved Permanently, 303 See Other, 307 Temporary Redirect + response.setStatusLine("1.1", 302, "found"); + response.setHeader("Location", "http://" + CROSS_ORIGIN + params.toString(), false); + return; + } + if (action === "test") { // ?action=test&policy=origin&name=name - var policy = params.get('policy'); - var name = params.get('name'); - var type = params.get('type'); + var policy = params.get("policy"); + var name = params.get("NAME"); + var type = params.get("type"); var result = getSharedState(SHARED_KEY); result = result ? JSON.parse(result) : {}; var referrerLevel = "none"; var test = {} - if (request.hasHeader('Referer')) { + if (request.hasHeader("Referer")) { var referrer = request.getHeader("Referer"); if (referrer.indexOf("referrer_testserver") > 0) { referrerLevel = "full"; @@ -119,7 +209,7 @@ function handleRequest(request, response) { } test.referrer = referrer; } else { - test.referrer = ''; + test.referrer = ""; } test.policy = referrerLevel; test.expected = policy; @@ -128,21 +218,35 @@ function handleRequest(request, response) { setSharedState(SHARED_KEY, JSON.stringify(result)); + if (type === "img") { + // return image + response.setHeader("Content-Type", "image/png"); + response.write(IMG_BYTES); + return; + } + if (type === "iframe") { + // return iframe page + response.write("I am the iframe"); + return; + } if (type === "link") { // forward link click to redirect URL to finish test - var loc = "http://" + BASE_URL + "?action=redirect"; - response.setStatusLine('1.1', 302, 'Found'); - response.setHeader('Location', loc, false); + var loc = "http://" + BASE_URL + "ACTION=redirect"; + response.setStatusLine("1.1", 302, "Found"); + response.setHeader("Location", loc, false); } return; } + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Content-Type", "text/html; charset=utf-8", false); + // parse test arguments and start test - var attributePolicy = params.get(ATTRIBUTE_POLICY) || ''; - var newAttributePolicy = params.get(NEW_ATTRIBUTE_POLICY) || ''; - var metaPolicy = params.get(META_POLICY) || ''; - var rel = params.get(REL) || ''; - var name = params.get(NAME); + var attributePolicy = params.get("ATTRIBUTE_POLICY") || ""; + var newAttributePolicy = params.get("NEW_ATTRIBUTE_POLICY") || ""; + var metaPolicy = params.get("META_POLICY") || ""; + var rel = params.get("REL") || ""; + var name = params.get("NAME"); // anchor & area var _getPage = createAETestPageUsingRefferer.bind(null, metaPolicy, attributePolicy, newAttributePolicy, name, rel); @@ -150,28 +254,55 @@ function handleRequest(request, response) { var _getAreaPage = _getPage.bind(null, buildAreaString); // aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aChangingMethod, aStringBuilder - if (action === 'generate-anchor-policy-test') { + if (action === "generate-anchor-policy-test") { response.write(_getAnchorPage()); return; } - if (action === 'generate-anchor-changing-policy-test-set-attribute') { - response.write(_getAnchorPage('setAttribute')); + if (action === "generate-anchor-changing-policy-test-set-attribute") { + response.write(_getAnchorPage("setAttribute")); return; } - if (action === 'generate-anchor-changing-policy-test-property') { - response.write(_getAnchorPage('property')); + if (action === "generate-anchor-changing-policy-test-property") { + response.write(_getAnchorPage("property")); return; } - if (action === 'generate-area-policy-test') { + if (action === "generate-area-policy-test") { response.write(_getAreaPage()); return; } - if (action === 'generate-area-changing-policy-test-set-attribute') { - response.write(_getAreaPage('setAttribute')); + if (action === "generate-area-changing-policy-test-set-attribute") { + response.write(_getAreaPage("setAttribute")); return; } - if (action === 'generate-area-changing-policy-test-property') { - response.write(_getAreaPage('property')); + if (action === "generate-area-changing-policy-test-property") { + response.write(_getAreaPage("property")); + return; + } + + // iframe + _getPage = createIframeTestPageUsingRefferer.bind(null, metaPolicy, attributePolicy, newAttributePolicy, name, ""); + + // aMetaPolicy, aAttributePolicy, aNewAttributePolicy, aName, aChangingMethod + if (action === "generate-iframe-policy-test") { + response.write(_getPage()); + return; + } + if (action === "generate-iframe-changing-policy-test-set-attribute") { + response.write(_getPage("setAttribute")); + return; + } + if (action === "generate-iframe-changing-policy-test-property") { + response.write(_getPage("property")); + return; + } + + // redirect tests with img and iframe + if (action === "generate-img-redirect-policy-test") { + response.write(createRedirectImgTestCase(params, attributePolicy)); + return; + } + if (action === "generate-iframe-redirect-policy-test") { + response.write(createIframeTestPageUsingRefferer(metaPolicy, attributePolicy, newAttributePolicy, name, params)); return; } diff --git a/dom/base/test/script_cloning_fileList.js b/dom/base/test/script_postmessages_fileList.js similarity index 100% rename from dom/base/test/script_cloning_fileList.js rename to dom/base/test/script_postmessages_fileList.js diff --git a/dom/base/test/test_anchor_area_referrer.html b/dom/base/test/test_anchor_area_referrer.html index 011377b35a..4aafa5f66a 100644 --- a/dom/base/test/test_anchor_area_referrer.html +++ b/dom/base/test/test_anchor_area_referrer.html @@ -9,22 +9,12 @@ diff --git a/dom/base/test/test_anchor_area_referrer_changing.html b/dom/base/test/test_anchor_area_referrer_changing.html index 36e7cf0a4a..db79d7b67d 100644 --- a/dom/base/test/test_anchor_area_referrer_changing.html +++ b/dom/base/test/test_anchor_area_referrer_changing.html @@ -15,16 +15,7 @@ + + + + + + + + + + + + diff --git a/dom/base/test/test_anchor_area_referrer_rel.html b/dom/base/test/test_anchor_area_referrer_rel.html new file mode 100644 index 0000000000..b3eb683148 --- /dev/null +++ b/dom/base/test/test_anchor_area_referrer_rel.html @@ -0,0 +1,50 @@ + + + + + Test anchor and area policy attribute for Bug 1174913 + + + + + + + + + + + + + diff --git a/dom/base/test/test_cloning_fileList.html b/dom/base/test/test_cloning_fileList.html deleted file mode 100644 index 613dfc6198..0000000000 --- a/dom/base/test/test_cloning_fileList.html +++ /dev/null @@ -1,85 +0,0 @@ - - - - Test for cloning FileList - - - - - -

- -

-
-
-
- - diff --git a/dom/base/test/test_iframe_referrer.html b/dom/base/test/test_iframe_referrer.html new file mode 100644 index 0000000000..ddbe590399 --- /dev/null +++ b/dom/base/test/test_iframe_referrer.html @@ -0,0 +1,55 @@ + + + + + Test iframe referrer policy attribute for Bug 1175736 + + + + + + + + + + + + diff --git a/dom/base/test/test_iframe_referrer_changing.html b/dom/base/test/test_iframe_referrer_changing.html new file mode 100644 index 0000000000..43e096cd42 --- /dev/null +++ b/dom/base/test/test_iframe_referrer_changing.html @@ -0,0 +1,50 @@ + + + + + Test iframe referrer policy attribute for Bug 1175736 + + + + + + + + + + + + diff --git a/dom/base/test/test_iframe_referrer_invalid.html b/dom/base/test/test_iframe_referrer_invalid.html new file mode 100644 index 0000000000..03abfc5c15 --- /dev/null +++ b/dom/base/test/test_iframe_referrer_invalid.html @@ -0,0 +1,70 @@ + + + + + Test iframe referrer policy attribute for Bug 1175736 + + + + + + + + + + + + diff --git a/dom/base/test/test_pluginAudioNotification.html b/dom/base/test/test_pluginAudioNotification.html new file mode 100644 index 0000000000..cbc68672b6 --- /dev/null +++ b/dom/base/test/test_pluginAudioNotification.html @@ -0,0 +1,117 @@ + + + + Test for audio controller in windows + + + + +
+
+ + + + + + diff --git a/dom/base/test/test_postMessages.html b/dom/base/test/test_postMessages.html new file mode 100644 index 0000000000..07c98011f7 --- /dev/null +++ b/dom/base/test/test_postMessages.html @@ -0,0 +1,256 @@ + + + + Test for postMessages cloning/transferring objects + + + + + + + + + diff --git a/dom/base/test/test_referrer_redirect.html b/dom/base/test/test_referrer_redirect.html new file mode 100644 index 0000000000..a00def9c7b --- /dev/null +++ b/dom/base/test/test_referrer_redirect.html @@ -0,0 +1,72 @@ + + + + + Test anchor and area policy attribute for Bug 1184781 + + + + + + + + + + + + + diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp index 4966109e20..c34c6fa130 100644 --- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -271,7 +271,7 @@ NeckoParent::AllocPFTPChannelParent(const PBrowserOrId& aBrowser, return nullptr; } PBOverrideStatus overrideStatus = PBOverrideStatusFromLoadContext(aSerialized); - FTPChannelParent *p = new FTPChannelParent(loadContext, overrideStatus); + FTPChannelParent *p = new FTPChannelParent(aBrowser, loadContext, overrideStatus); p->AddRef(); return p; } diff --git a/netwerk/protocol/ftp/FTPChannelParent.cpp b/netwerk/protocol/ftp/FTPChannelParent.cpp index c921ae71ca..0eea8e0f1b 100644 --- a/netwerk/protocol/ftp/FTPChannelParent.cpp +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -6,10 +6,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/net/FTPChannelParent.h" +#include "mozilla/dom/TabParent.h" #include "nsFTPChannel.h" #include "nsNetCID.h" #include "nsNetUtil.h" +#include "nsQueryObject.h" #include "nsFtpProtocolHandler.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPromptProvider.h" #include "nsIEncodedChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIForcePendingChannel.h" @@ -22,6 +26,7 @@ #include "nsIOService.h" #include "mozilla/LoadInfo.h" +using namespace mozilla::dom; using namespace mozilla::ipc; #undef LOG @@ -30,7 +35,9 @@ using namespace mozilla::ipc; namespace mozilla { namespace net { -FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus) +FTPChannelParent::FTPChannelParent(const PBrowserOrId& aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus) : mIPCClosed(false) , mLoadContext(aLoadContext) , mPBOverride(aOverrideStatus) @@ -41,8 +48,12 @@ FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatu { nsIProtocolHandler* handler; CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler); - NS_ASSERTION(handler, "no ftp handler"); - + MOZ_ASSERT(handler, "no ftp handler"); + + if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) { + mTabParent = static_cast(aIframeEmbedding.get_PBrowserParent()); + } + mObserver = new OfflineObserver(this); } @@ -450,6 +461,21 @@ FTPChannelParent::Delete() NS_IMETHODIMP FTPChannelParent::GetInterface(const nsIID& uuid, void** result) { + if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) || + uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) { + if (mTabParent) { + return mTabParent->QueryInterface(uuid, result); + } + } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) || + uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr provider(do_QueryObject(mTabParent)); + if (provider) { + return provider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL, + uuid, + result); + } + } + // Only support nsILoadContext if child channel's callbacks did too if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) { nsCOMPtr copy = mLoadContext; diff --git a/netwerk/protocol/ftp/FTPChannelParent.h b/netwerk/protocol/ftp/FTPChannelParent.h index 3d70443ff7..09a97cf8b3 100644 --- a/netwerk/protocol/ftp/FTPChannelParent.h +++ b/netwerk/protocol/ftp/FTPChannelParent.h @@ -19,6 +19,12 @@ class nsILoadContext; namespace mozilla { + +namespace dom { +class TabParent; +class PBrowserOrId; +} // namespace dom + namespace net { class FTPChannelParent final : public PFTPChannelParent @@ -36,7 +42,9 @@ public: NS_DECL_NSIINTERFACEREQUESTOR NS_DECL_NSICHANNELEVENTSINK - FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus); + FTPChannelParent(const dom::PBrowserOrId& aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus); bool Init(const FTPChannelCreationArgs& aOpenArgs); @@ -110,6 +118,7 @@ protected: // when we call ResumeForDiversion. bool mSuspendedForDiversion; nsRefPtr mObserver; + nsRefPtr mTabParent; }; } // namespace net diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index fd00ae6343..b433246e02 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -2676,10 +2676,15 @@ WebSocketChannel::ApplyForAdmission() MOZ_ASSERT(!mCancelable); - return pps->AsyncResolve(mHttpChannel, - nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | - nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, - this, getter_AddRefs(mCancelable)); + nsresult rv; + rv = pps->AsyncResolve(mHttpChannel, + nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY | + nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL, + this, getter_AddRefs(mCancelable)); + NS_ASSERTION(NS_FAILED(rv) || mCancelable, + "nsIProtocolProxyService::AsyncResolve succeeded but didn't " + "return a cancelable object!"); + return rv; } // Called after both OnStartRequest and OnTransportAvailable have @@ -2786,7 +2791,7 @@ WebSocketChannel::OnProxyAvailable(nsICancelable *aRequest, nsIChannel *aChannel return NS_OK; } - MOZ_ASSERT(aRequest == mCancelable); + MOZ_ASSERT(!mCancelable || (aRequest == mCancelable)); mCancelable = nullptr; nsAutoCString type; diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp new file mode 100644 index 0000000000..d09e23edcd --- /dev/null +++ b/xpcom/ds/Tokenizer.cpp @@ -0,0 +1,389 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "Tokenizer.h" + +#include "nsUnicharUtils.h" +#include "mozilla/CheckedInt.h" + +namespace mozilla { + +static const char sWhitespaces[] = " \t"; + +Tokenizer::Tokenizer(const nsACString& aSource) + : mPastEof(false) + , mHasFailed(false) + , mWhitespaces(sWhitespaces) +{ + aSource.BeginReading(mCursor); + mRecord = mRollback = mCursor; + aSource.EndReading(mEnd); +} + +bool +Tokenizer::Next(Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = Parse(aToken); + mPastEof = aToken.Type() == TOKEN_EOF; + mHasFailed = false; + return true; +} + +bool +Tokenizer::Check(const TokenType aTokenType, Token& aResult) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + nsACString::const_char_iterator next = Parse(aResult); + if (aTokenType != aResult.Type()) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + mPastEof = aResult.Type() == TOKEN_EOF; + return true; +} + +bool +Tokenizer::Check(const Token& aToken) +{ + if (!HasInput()) { + mHasFailed = true; + return false; + } + + Token parsed; + nsACString::const_char_iterator next = Parse(parsed); + if (!aToken.Equals(parsed)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + mCursor = next; + mPastEof = parsed.Type() == TOKEN_EOF; + return true; +} + +bool +Tokenizer::HasFailed() const +{ + return mHasFailed; +} + +void +Tokenizer::SkipWhites() +{ + if (!CheckWhite()) { + return; + } + + nsACString::const_char_iterator rollback = mRollback; + while (CheckWhite()) { + } + + mHasFailed = false; + mRollback = rollback; +} + +bool +Tokenizer::CheckChar(bool (*aClassifier)(const char aChar)) +{ + if (!aClassifier) { + MOZ_ASSERT(false); + return false; + } + + if (!HasInput() || mCursor == mEnd) { + mHasFailed = true; + return false; + } + + if (!aClassifier(*mCursor)) { + mHasFailed = true; + return false; + } + + mRollback = mCursor; + ++mCursor; + mHasFailed = false; + return true; +} + +void +Tokenizer::Rollback() +{ + MOZ_ASSERT(mCursor > mRollback || mPastEof, + "Tokenizer::Rollback() cannot use twice or before any parsing"); + + mPastEof = false; + mHasFailed = false; + mCursor = mRollback; +} + +void +Tokenizer::Record(ClaimInclusion aInclude) +{ + mRecord = aInclude == INCLUDE_LAST + ? mRollback + : mCursor; +} + +void +Tokenizer::Claim(nsACString& aResult, ClaimInclusion aInclusion) +{ + nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST + ? mRollback + : mCursor; + aResult.Assign(Substring(mRecord, close)); +} + +// protected + +bool +Tokenizer::HasInput() const +{ + return !mPastEof; +} + +nsACString::const_char_iterator +Tokenizer::Parse(Token& aToken) const +{ + if (mCursor == mEnd) { + aToken = Token::EndOfFile(); + return mEnd; + } + + nsACString::const_char_iterator next = mCursor; + + enum State { + PARSE_INTEGER, + PARSE_WORD, + PARSE_CRLF, + PARSE_LF, + PARSE_WS, + PARSE_CHAR, + } state; + + if (IsWordFirst(*next)) { + state = PARSE_WORD; + } else if (IsNumber(*next)) { + state = PARSE_INTEGER; + } else if (*next == '\r') { + state = PARSE_CRLF; + } else if (*next == '\n') { + state = PARSE_LF; + } else if (strchr(mWhitespaces, *next)) { // not UTF-8 friendly? + state = PARSE_WS; + } else { + state = PARSE_CHAR; + } + + mozilla::CheckedInt64 resultingNumber = 0; + + while (next < mEnd) { + switch (state) { + case PARSE_INTEGER: + // Keep it simple for now + resultingNumber *= 10; + resultingNumber += static_cast(*next - '0'); + + ++next; + if (IsEnd(next) || !IsNumber(*next)) { + if (!resultingNumber.isValid()) { + aToken = Token::Error(); + } else { + aToken = Token::Number(resultingNumber.value()); + } + return next; + } + break; + + case PARSE_WORD: + ++next; + if (IsEnd(next) || !IsWord(*next)) { + aToken = Token::Word(Substring(mCursor, next)); + return next; + } + break; + + case PARSE_CRLF: + ++next; + if (!IsEnd(next) && *next == '\n') { // LF is optional + ++next; + } + aToken = Token::NewLine(); + return next; + + case PARSE_LF: + ++next; + aToken = Token::NewLine(); + return next; + + case PARSE_WS: + ++next; + aToken = Token::Whitespace(); + return next; + + case PARSE_CHAR: + ++next; + aToken = Token::Char(*mCursor); + return next; + } // switch (state) + } // while (next < end) + + return next; +} + +bool +Tokenizer::IsEnd(const nsACString::const_char_iterator& caret) const +{ + return caret == mEnd; +} + +bool +Tokenizer::IsWordFirst(const char aInput) const +{ + // TODO: make this fully work with unicode + return (ToLowerCase(static_cast(aInput)) != + ToUpperCase(static_cast(aInput))) || + '_' == aInput; +} + +bool +Tokenizer::IsWord(const char aInput) const +{ + return IsWordFirst(aInput) || IsNumber(aInput); +} + +bool +Tokenizer::IsNumber(const char aInput) const +{ + // TODO: are there unicode numbers? + return aInput >= '0' && aInput <= '9'; +} + +// Tokenizer::Token + +// static +Tokenizer::Token +Tokenizer::Token::Word(const nsACString& aValue) +{ + Token t; + t.mType = TOKEN_WORD; + t.mWord = aValue; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::Char(const char aValue) +{ + Token t; + t.mType = TOKEN_CHAR; + t.mChar = aValue; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::Number(const int64_t aValue) +{ + Token t; + t.mType = TOKEN_INTEGER; + t.mInteger = aValue; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::Whitespace() +{ + Token t; + t.mType = TOKEN_WS; + t.mChar = '\0'; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::NewLine() +{ + Token t; + t.mType = TOKEN_EOL; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::EndOfFile() +{ + Token t; + t.mType = TOKEN_EOF; + return t; +} + +// static +Tokenizer::Token +Tokenizer::Token::Error() +{ + Token t; + t.mType = TOKEN_ERROR; + return t; +} + +bool +Tokenizer::Token::Equals(const Token& aOther) const +{ + if (mType != aOther.mType) { + return false; + } + + switch (mType) { + case TOKEN_INTEGER: + return AsInteger() == aOther.AsInteger(); + case TOKEN_WORD: + return AsString() == aOther.AsString(); + case TOKEN_CHAR: + return AsChar() == aOther.AsChar(); + default: + return true; + } +} + +char +Tokenizer::Token::AsChar() const +{ + MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS); + return mChar; +} + +nsCString +Tokenizer::Token::AsString() const +{ + MOZ_ASSERT(mType == TOKEN_WORD); + return mWord; +} + +int64_t +Tokenizer::Token::AsInteger() const +{ + MOZ_ASSERT(mType == TOKEN_INTEGER); + return mInteger; +} + +} // mozilla diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h new file mode 100644 index 0000000000..630d55daf4 --- /dev/null +++ b/xpcom/ds/Tokenizer.h @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef Tokenizer_h__ +#define Tokenizer_h__ + +#include "nsString.h" + +namespace mozilla { + +/** + * This is a simple implementation of a lexical analyzer or maybe better + * called a tokenizer. It doesn't allow any user dictionaries or + * user define token types. + * + * It is limited only to ASCII input for now. UTF-8 or any other input + * encoding must yet be implemented. + */ +class Tokenizer { +public: + /** + * The analyzer works with elements in the input cut to a sequence of token + * where each token has an elementary type + */ + enum TokenType { + TOKEN_UNKNOWN, + TOKEN_ERROR, + TOKEN_INTEGER, + TOKEN_WORD, + TOKEN_CHAR, + TOKEN_WS, + TOKEN_EOL, + TOKEN_EOF + }; + + /** + * Class holding the type and the value of a token. It can be manually created + * to allow checks against it via methods of Tokenizer or are results of some of + * the Tokenizer's methods. + */ + class Token { + TokenType mType; + nsCString mWord; + char mChar; + int64_t mInteger; + + public: + Token() : mType(TOKEN_UNKNOWN), mChar(0), mInteger(0) {} + + // Static constructors of tokens by type and value + static Token Word(const nsACString& aWord); + static Token Char(const char aChar); + static Token Number(const int64_t aNumber); + static Token Whitespace(); + static Token NewLine(); + static Token EndOfFile(); + static Token Error(); + + // Compares the two tokens, type must be identical and value + // of one of the tokens must be 'any' or equal. + bool Equals(const Token& aOther) const; + + TokenType Type() const { return mType; } + char AsChar() const; + nsCString AsString() const; + int64_t AsInteger() const; + }; + +public: + explicit Tokenizer(const nsACString& aSource); + + /** + * Some methods are collecting the input as it is being parsed to obtain + * a substring between particular syntax bounderies defined by any recursive + * descent parser or simple parser the Tokenizer is used to read the input for. + */ + enum ClaimInclusion { + /** + * Include resulting (or passed) token of the last lexical analyzer operation in the result. + */ + INCLUDE_LAST, + /** + * Do not include it. + */ + EXCLUDE_LAST + }; + + /** + * When there is still anything to read from the input, tokenize it, store the token type + * and value to aToken result and shift the cursor past this just parsed token. Each call + * to Next() reads another token from the input and shifts the cursor. + * Returns false if we have passed the end of the input. + */ + MOZ_WARN_UNUSED_RESULT + bool Next(Token& aToken); + + /** + * Parse the token on the input read cursor position, check its type is equal to aTokenType + * and if so, put it into aResult, shift the cursor and return true. Otherwise, leave + * the input read cursor position intact and return false. + */ + MOZ_WARN_UNUSED_RESULT + bool Check(const TokenType aTokenType, Token& aResult); + /** + * Same as above method, just compares both token type and token value passed in aToken. + * When both the type and the value equals, shift the cursor and return true. Otherwise + * return false. + */ + MOZ_WARN_UNUSED_RESULT + bool Check(const Token& aToken); + + /** + * Return false iff the last Check*() call has returned false or when we've read past + * the end of the input string. + */ + MOZ_WARN_UNUSED_RESULT + bool HasFailed() const; + + /** + * Skips any occurence of whitespaces specified in mWhitespaces member. + */ + void SkipWhites(); + + // These are mostly shortcuts for the Check() methods above. + + /** + * Check whitespace character is present. + */ + MOZ_WARN_UNUSED_RESULT + bool CheckWhite() { return Check(Token::Whitespace()); } + /** + * Check there is a single character on the read cursor position. If so, shift the read + * cursor position and return true. Otherwise false. + */ + MOZ_WARN_UNUSED_RESULT + bool CheckChar(const char aChar) { return Check(Token::Char(aChar)); } + /** + * This is a customizable version of CheckChar. aClassifier is a function called with + * value of the character on the current input read position. If this user function + * returns true, read cursor is shifted and true returned. Otherwise false. + * The user classifiction function is not called when we are at or past the end and + * false is immediately returned. + */ + MOZ_WARN_UNUSED_RESULT + bool CheckChar(bool (*aClassifier)(const char aChar)); + /** + * Shortcut for direct word check. + */ + template + MOZ_WARN_UNUSED_RESULT + bool CheckWord(const char (&aWord)[N]) { return Check(Token::Word(nsLiteralCString(aWord))); } + /** + * Checks \r, \n or \r\n. + */ + MOZ_WARN_UNUSED_RESULT + bool CheckEOL() { return Check(Token::NewLine()); } + /** + * Checks we are at the end of the input string reading. If so, shift past the end + * and returns true. Otherwise does nothing and returns false. + */ + MOZ_WARN_UNUSED_RESULT + bool CheckEOF() { return Check(Token::EndOfFile()); } + + /** + * Returns the read cursor position back as it was before the last call of any parsing + * method of Tokenizer (Next, Check*, Skip*) so that the last operation can be repeated. + * Rollback cannot be used multiple times, it only reverts the last successfull parse + * operation. It also cannot be used before any parsing operation has been called + * on the Tokenizer. + */ + void Rollback(); + + /** + * Start the process of recording. Based on aInclude value the begining of the recorded + * sub-string is at the current position (EXCLUDE_LAST) or at the position before the last + * parsed token (INCLUDE_LAST). + */ + void Record(ClaimInclusion aInclude = EXCLUDE_LAST); + /** + * Claim result of the record started with Record() call before. Depending on aInclude + * the ending of the sub-string result includes or excludes the last parsed or checked + * token. + */ + void Claim(nsACString& aResult, ClaimInclusion aInclude = EXCLUDE_LAST); + +protected: + // true if we have already read the EOF token. + bool HasInput() const; + // Main parsing function, it doesn't shift the read cursor, just returns the next + // token position. + nsACString::const_char_iterator Parse(Token& aToken) const; + // Is read cursor at the end? + bool IsEnd(const nsACString::const_char_iterator& caret) const; + // Is read cursor on a character that is a word start? + bool IsWordFirst(const char aInput) const; + // Is read cursor on a character that is an in-word letter? + bool IsWord(const char aInput) const; + // Is read cursor on a character that is a valid number? + // TODO - support multiple radix + bool IsNumber(const char aInput) const; + +private: + Tokenizer() = delete; + + // true iff we have already read the EOF token + bool mPastEof; + // true iff the last Check*() call has returned false, reverts to true on Rollback() call + bool mHasFailed; + + // Customizable list of whitespaces + char const* mWhitespaces; + + // All these point to the original buffer passed to the Tokenizer + nsACString::const_char_iterator mRecord; // Position where the recorded sub-string for Claim() is + nsACString::const_char_iterator mRollback; // Position of the previous token start + nsACString::const_char_iterator mCursor; // Position of the current (actually next to read) token start + nsACString::const_char_iterator mEnd; // End of the input position +}; + +} // mozilla + +#endif // Tokenizer_h__ diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build index 3cb301a512..b186062fb2 100644 --- a/xpcom/ds/moz.build +++ b/xpcom/ds/moz.build @@ -62,6 +62,7 @@ EXPORTS += [ EXPORTS.mozilla += [ 'StickyTimeDuration.h', + 'Tokenizer.h', ] UNIFIED_SOURCES += [ @@ -79,6 +80,7 @@ UNIFIED_SOURCES += [ 'nsSupportsArrayEnumerator.cpp', 'nsSupportsPrimitives.cpp', 'nsVariant.cpp', + 'Tokenizer.cpp', ] # These two files cannot be built in unified mode because they use the diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp new file mode 100644 index 0000000000..a43647573e --- /dev/null +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -0,0 +1,292 @@ +#include "mozilla/Tokenizer.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +static bool IsOperator(char const c) +{ + return c == '+' || c == '*'; +} + +static bool HttpHeaderCharacter(char const c) +{ + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + (c == '_') || + (c == '-'); +} + +TEST(Tokenizer, HTTPResponse) +{ + Tokenizer::Token t; + + // Real life test, HTTP response + + Tokenizer p(NS_LITERAL_CSTRING( + "HTTP/1.0 304 Not modified\r\n" + "ETag: hallo\r\n" + "Content-Length: 16\r\n" + "\r\n" + "This is the body")); + + EXPECT_TRUE(p.CheckWord("HTTP")); + EXPECT_TRUE(p.CheckChar('/')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 1); + EXPECT_TRUE(p.CheckChar('.')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 0); + p.SkipWhites(); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 304); + p.SkipWhites(); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL); + EXPECT_FALSE(p.HasFailed()); + nsAutoCString h; + p.Claim(h); + EXPECT_TRUE(h == "Not modified"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)); + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "ETag"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL); + EXPECT_FALSE(p.HasFailed()); + p.Claim(h); + EXPECT_TRUE(h == "hallo"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)); + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "Content-Length"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.AsInteger() == 16); + EXPECT_TRUE(p.CheckEOL()); + + EXPECT_TRUE(p.CheckEOL()); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOF); + nsAutoCString b; + p.Claim(b); + EXPECT_TRUE(b == "This is the body"); +} + +TEST(Tokenizer, Main) +{ + Tokenizer::Token t; + + // Synthetic code-specific test + + Tokenizer p(NS_LITERAL_CSTRING("test123 ,15 \t*\r\n%xx,-15\r\r")); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t.AsString() == "test123"); + + Tokenizer::Token u; + EXPECT_FALSE(p.Check(u)); + + EXPECT_FALSE(p.CheckChar('!')); + + EXPECT_FALSE(p.Check(Tokenizer::Token::Number(123))); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_FALSE(p.CheckChar(IsOperator)); + + EXPECT_TRUE(p.CheckWhite()); + + p.SkipWhites(); + + EXPECT_FALSE(p.CheckWhite()); + + p.Rollback(); + + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + + p.Record(Tokenizer::EXCLUDE_LAST); + + EXPECT_TRUE(p.CheckChar(IsOperator)); + + p.Rollback(); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '*'); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '%'); + + nsAutoCString claim; + p.Claim(claim, Tokenizer::EXCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n"); + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n%"); + + p.Rollback(); + EXPECT_TRUE(p.CheckChar('%')); + + p.Record(Tokenizer::INCLUDE_LAST); + + EXPECT_FALSE(p.CheckWord("xy")); + + EXPECT_TRUE(p.CheckWord("xx")); + + + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "%xx"); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == ','); + + EXPECT_TRUE(p.CheckChar('-')); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.CheckEOF()); +} + +TEST(Tokenizer, SingleWord) +{ + // Single word with numbers in it test + + Tokenizer p(NS_LITERAL_CSTRING("test123")); + + EXPECT_TRUE(p.CheckWord("test123")); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, EndingAfterNumber) +{ + // An end handling after a number + + Tokenizer p(NS_LITERAL_CSTRING("123")); + + EXPECT_FALSE(p.CheckWord("123")); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123))); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, BadInteger) +{ + Tokenizer::Token t; + + // A bad integer test + + Tokenizer p(NS_LITERAL_CSTRING("189234891274981758617846178651647620587135")); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_ERROR); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, CheckExpectedTokenValue) +{ + Tokenizer::Token t; + + // Check expected token value test + + Tokenizer p(NS_LITERAL_CSTRING("blue velvet")); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "blue"); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_WORD, t)); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "velvet"); + + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.Next(t)); +} + +TEST(Tokenizer, HasFailed) +{ + Tokenizer::Token t; + + // HasFailed test + + Tokenizer p1(NS_LITERAL_CSTRING("a b")); + + while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(p1.HasFailed()); + + + Tokenizer p2(NS_LITERAL_CSTRING("a b")); + + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.CheckChar(HttpHeaderCharacter)); + EXPECT_FALSE(p2.HasFailed()); + p2.SkipWhites(); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + + while (p2.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(p2.HasFailed()); +} diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build index 29202d7b5f..7c83a1fc80 100644 --- a/xpcom/tests/gtest/moz.build +++ b/xpcom/tests/gtest/moz.build @@ -22,6 +22,7 @@ UNIFIED_SOURCES += [ 'TestThreadPool.cpp', 'TestThreads.cpp', 'TestTimeStamp.cpp', + 'TestTokenizer.cpp', 'TestUTF.cpp', 'TestXPIDLString.cpp', ]